Merge lp:~elopio/ubuntu-ui-toolkit/datepicker-autopilot_helper into lp:ubuntu-ui-toolkit/staging
- datepicker-autopilot_helper
- Merge into staging
Status: | Merged |
---|---|
Approved by: | Cris Dywan |
Approved revision: | 1049 |
Merged at revision: | 1064 |
Proposed branch: | lp:~elopio/ubuntu-ui-toolkit/datepicker-autopilot_helper |
Merge into: | lp:ubuntu-ui-toolkit/staging |
Prerequisite: | lp:~elopio/ubuntu-ui-toolkit/swipe_into_view |
Diff against target: |
611 lines (+419/-37) 9 files modified
modules/Ubuntu/Components/Pickers/DatePicker.qml (+3/-5) modules/Ubuntu/Components/Pickers/PickerRow.qml (+1/-1) tests/autopilot/ubuntuuitoolkit/__init__.py (+2/-0) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/__init__.py (+2/-0) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_flickable.py (+30/-20) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_qquicklistview.py (+27/-10) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/pickers.py (+206/-0) tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_date_picker.py (+147/-0) tests/unit_x11/tst_components/tst_datepicker.qml (+1/-1) |
To merge this branch: | bzr merge lp:~elopio/ubuntu-ui-toolkit/datepicker-autopilot_helper |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Approve | |
Cris Dywan | Approve | ||
Allan LeSage | Pending | ||
Review via email: mp+218909@code.launchpad.net |
This proposal supersedes a proposal from 2014-05-07.
Commit message
Added autopilot helpers to pick a date from a date picker.
Description of the change
Next branch will add support for picking a time.
Allan LeSage (allanlesage) wrote : Posted in a previous version of this proposal | # |
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1041
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Leo Arias (elopio) wrote : Posted in a previous version of this proposal | # |
> Ok I think understand this change, appears thoroughly tested, nice work :) .
> I'm assuming that the comment on l.535 doesn't apply to the present merge
> proposal.
It does. I was just trying to test it the wrong way. Now I added a couple of tests for that.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1045
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Cris Dywan (kalikiana) wrote : | # |
8 - Qt.formatDate(
9 + Qt.formatDate(
Why is this change needed?
It looks overall nice.
Leo Arias (elopio) wrote : | # |
that's because there's no such thing as mmmm. http://
the example doesn't work.
Cris Dywan (kalikiana) wrote : | # |
Nice catch! Thanks
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1046
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Cris Dywan (kalikiana) wrote : | # |
FAIL! : tst_UCAlarms:
Loc: [tst_alarms.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1046
http://
Executed test runs:
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Autolanding.
More details in the following jenkins job:
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1047
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1048
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1049
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Autolanding.
More details in the following jenkins job:
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
Cris Dywan (kalikiana) wrote : | # |
FAIL! : tst_UCAlarms:
Loc: [tst_alarms.
PS Jenkins bot (ps-jenkins) : | # |
Preview Diff
1 | === modified file 'modules/Ubuntu/Components/Pickers/DatePicker.qml' | |||
2 | --- modules/Ubuntu/Components/Pickers/DatePicker.qml 2014-04-23 08:50:20 +0000 | |||
3 | +++ modules/Ubuntu/Components/Pickers/DatePicker.qml 2014-05-20 07:32:50 +0000 | |||
4 | @@ -42,7 +42,7 @@ | |||
5 | 42 | Column { | 42 | Column { |
6 | 43 | Label { | 43 | Label { |
7 | 44 | text: "Selected date: W" + datePicker.week + " - " + | 44 | text: "Selected date: W" + datePicker.week + " - " + |
9 | 45 | Qt.formatDate(datePicker.date, "dddd, dd-mmmm-yyyy") | 45 | Qt.formatDate(datePicker.date, "dddd, dd-MMMM-yyyy") |
10 | 46 | } | 46 | } |
11 | 47 | DatePicker { | 47 | DatePicker { |
12 | 48 | id: datePicker | 48 | id: datePicker |
13 | @@ -61,7 +61,7 @@ | |||
14 | 61 | 61 | ||
15 | 62 | Column { | 62 | Column { |
16 | 63 | Label { | 63 | Label { |
18 | 64 | text: "Selected month: " + Qt.formatDate(datePicker.date, "mmmm-yyyy") | 64 | text: "Selected month: " + Qt.formatDate(datePicker.date, "MMMM-yyyy") |
19 | 65 | } | 65 | } |
20 | 66 | DatePicker { | 66 | DatePicker { |
21 | 67 | id: datePicker | 67 | id: datePicker |
22 | @@ -117,7 +117,7 @@ | |||
23 | 117 | 117 | ||
24 | 118 | Column { | 118 | Column { |
25 | 119 | Label { | 119 | Label { |
27 | 120 | text: "Selected date: " + Qt.formatDate(datePicker.date, "dddd, dd-mmmm-yyyy") | 120 | text: "Selected date: " + Qt.formatDate(datePicker.date, "dddd, dd-MMMM-yyyy") |
28 | 121 | } | 121 | } |
29 | 122 | DatePicker { | 122 | DatePicker { |
30 | 123 | id: datePicker | 123 | id: datePicker |
31 | @@ -667,5 +667,3 @@ | |||
32 | 667 | } | 667 | } |
33 | 668 | } | 668 | } |
34 | 669 | } | 669 | } |
35 | 670 | |||
36 | 671 | |||
37 | 672 | 670 | ||
38 | === modified file 'modules/Ubuntu/Components/Pickers/PickerRow.qml' | |||
39 | --- modules/Ubuntu/Components/Pickers/PickerRow.qml 2014-04-23 08:50:20 +0000 | |||
40 | +++ modules/Ubuntu/Components/Pickers/PickerRow.qml 2014-05-20 07:32:50 +0000 | |||
41 | @@ -96,7 +96,7 @@ | |||
42 | 96 | } | 96 | } |
43 | 97 | delegate: PickerDelegate { | 97 | delegate: PickerDelegate { |
44 | 98 | Label { | 98 | Label { |
46 | 99 | objectName: "PickerRow_PickerLabel" | 99 | objectName: "PickerRow_PickerLabel" + (pickerModel ? modelData : "") |
47 | 100 | text: pickerModel ? pickerModel.text(modelData) : "" | 100 | text: pickerModel ? pickerModel.text(modelData) : "" |
48 | 101 | anchors.fill: parent | 101 | anchors.fill: parent |
49 | 102 | verticalAlignment: Text.AlignVCenter | 102 | verticalAlignment: Text.AlignVCenter |
50 | 103 | 103 | ||
51 | === modified file 'tests/autopilot/ubuntuuitoolkit/__init__.py' | |||
52 | --- tests/autopilot/ubuntuuitoolkit/__init__.py 2014-04-30 00:46:11 +0000 | |||
53 | +++ tests/autopilot/ubuntuuitoolkit/__init__.py 2014-05-20 07:32:50 +0000 | |||
54 | @@ -30,6 +30,7 @@ | |||
55 | 30 | 'listitems', | 30 | 'listitems', |
56 | 31 | 'MainView', | 31 | 'MainView', |
57 | 32 | 'OptionSelector', | 32 | 'OptionSelector', |
58 | 33 | 'pickers', | ||
59 | 33 | 'popups', | 34 | 'popups', |
60 | 34 | 'QQuickFlickable', | 35 | 'QQuickFlickable', |
61 | 35 | 'QQuickListView', | 36 | 'QQuickListView', |
62 | @@ -59,6 +60,7 @@ | |||
63 | 59 | listitems, | 60 | listitems, |
64 | 60 | MainView, | 61 | MainView, |
65 | 61 | OptionSelector, | 62 | OptionSelector, |
66 | 63 | pickers, | ||
67 | 62 | popups, | 64 | popups, |
68 | 63 | QQuickFlickable, | 65 | QQuickFlickable, |
69 | 64 | QQuickListView, | 66 | QQuickListView, |
70 | 65 | 67 | ||
71 | === modified file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/__init__.py' | |||
72 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/__init__.py 2014-04-30 00:46:11 +0000 | |||
73 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/__init__.py 2014-05-20 07:32:50 +0000 | |||
74 | @@ -26,6 +26,7 @@ | |||
75 | 26 | 'listitems', | 26 | 'listitems', |
76 | 27 | 'MainView', | 27 | 'MainView', |
77 | 28 | 'OptionSelector', | 28 | 'OptionSelector', |
78 | 29 | 'pickers', | ||
79 | 29 | 'popups', | 30 | 'popups', |
80 | 30 | 'QQuickFlickable', | 31 | 'QQuickFlickable', |
81 | 31 | 'QQuickListView', | 32 | 'QQuickListView', |
82 | @@ -52,6 +53,7 @@ | |||
83 | 52 | from ubuntuuitoolkit._custom_proxy_objects._optionselector import ( | 53 | from ubuntuuitoolkit._custom_proxy_objects._optionselector import ( |
84 | 53 | OptionSelector | 54 | OptionSelector |
85 | 54 | ) | 55 | ) |
86 | 56 | from ubuntuuitoolkit._custom_proxy_objects import pickers | ||
87 | 55 | from ubuntuuitoolkit._custom_proxy_objects import popups | 57 | from ubuntuuitoolkit._custom_proxy_objects import popups |
88 | 56 | from ubuntuuitoolkit._custom_proxy_objects._qquicklistview import ( | 58 | from ubuntuuitoolkit._custom_proxy_objects._qquicklistview import ( |
89 | 57 | QQuickListView | 59 | QQuickListView |
90 | 58 | 60 | ||
91 | === modified file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_flickable.py' | |||
92 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_flickable.py 2014-04-30 00:46:11 +0000 | |||
93 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_flickable.py 2014-05-20 07:32:50 +0000 | |||
94 | @@ -38,21 +38,7 @@ | |||
95 | 38 | return min(containers_bottom) | 38 | return min(containers_bottom) |
96 | 39 | 39 | ||
97 | 40 | 40 | ||
113 | 41 | class QQuickFlickable(_common.UbuntuUIToolkitCustomProxyObjectBase): | 41 | class Scrollable(_common.UbuntuUIToolkitCustomProxyObjectBase): |
99 | 42 | |||
100 | 43 | @autopilot_logging.log_action(logger.info) | ||
101 | 44 | def swipe_child_into_view(self, child): | ||
102 | 45 | """Make the child visible. | ||
103 | 46 | |||
104 | 47 | Currently it works only when the object needs to be swiped vertically. | ||
105 | 48 | TODO implement horizontal swiping. --elopio - 2014-03-21 | ||
106 | 49 | |||
107 | 50 | """ | ||
108 | 51 | containers = self._get_containers() | ||
109 | 52 | if not self._is_child_visible(child, containers): | ||
110 | 53 | self._swipe_non_visible_child_into_view(child, containers) | ||
111 | 54 | else: | ||
112 | 55 | logger.debug('The element is already visible.') | ||
114 | 56 | 42 | ||
115 | 57 | def _get_containers(self): | 43 | def _get_containers(self): |
116 | 58 | """Return a list with the containers to take into account when swiping. | 44 | """Return a list with the containers to take into account when swiping. |
117 | @@ -94,6 +80,35 @@ | |||
118 | 94 | return (object_center >= visible_top and | 80 | return (object_center >= visible_top and |
119 | 95 | object_center <= visible_bottom) | 81 | object_center <= visible_bottom) |
120 | 96 | 82 | ||
121 | 83 | def _slow_drag(self, start_x, stop_x, start_y, stop_y): | ||
122 | 84 | # If we drag too fast, we end up scrolling more than what we | ||
123 | 85 | # should, sometimes missing the element we are looking for. | ||
124 | 86 | # I found that when the flickDeceleration is 1500, the rate should be | ||
125 | 87 | # 5 and that when it's 100, the rate should be 1. With those two points | ||
126 | 88 | # we can get that the following equation. | ||
127 | 89 | # XXX The deceleration might not be linear with respect to the rate, | ||
128 | 90 | # but this works for the two types of scrollables we have for now. | ||
129 | 91 | # --elopio - 2014-05-08 | ||
130 | 92 | rate = (self.flickDeceleration + 250) / 350 | ||
131 | 93 | self.pointing_device.drag(start_x, start_y, stop_x, stop_y, rate=rate) | ||
132 | 94 | |||
133 | 95 | |||
134 | 96 | class QQuickFlickable(Scrollable): | ||
135 | 97 | |||
136 | 98 | @autopilot_logging.log_action(logger.info) | ||
137 | 99 | def swipe_child_into_view(self, child): | ||
138 | 100 | """Make the child visible. | ||
139 | 101 | |||
140 | 102 | Currently it works only when the object needs to be swiped vertically. | ||
141 | 103 | TODO implement horizontal swiping. --elopio - 2014-03-21 | ||
142 | 104 | |||
143 | 105 | """ | ||
144 | 106 | containers = self._get_containers() | ||
145 | 107 | if not self._is_child_visible(child, containers): | ||
146 | 108 | self._swipe_non_visible_child_into_view(child, containers) | ||
147 | 109 | else: | ||
148 | 110 | logger.debug('The element is already visible.') | ||
149 | 111 | |||
150 | 97 | @autopilot_logging.log_action(logger.info) | 112 | @autopilot_logging.log_action(logger.info) |
151 | 98 | def _swipe_non_visible_child_into_view(self, child, containers): | 113 | def _swipe_non_visible_child_into_view(self, child, containers): |
152 | 99 | original_content_y = self.contentY | 114 | original_content_y = self.contentY |
153 | @@ -150,11 +165,6 @@ | |||
154 | 150 | self.dragging.wait_for(False) | 165 | self.dragging.wait_for(False) |
155 | 151 | self.moving.wait_for(False) | 166 | self.moving.wait_for(False) |
156 | 152 | 167 | ||
157 | 153 | def _slow_drag(self, start_x, stop_x, start_y, stop_y): | ||
158 | 154 | # If we drag too fast, we end up scrolling more than what we | ||
159 | 155 | # should, sometimes missing the element we are looking for. | ||
160 | 156 | self.pointing_device.drag(start_x, start_y, stop_x, stop_y, rate=5) | ||
161 | 157 | |||
162 | 158 | @autopilot_logging.log_action(logger.info) | 168 | @autopilot_logging.log_action(logger.info) |
163 | 159 | def _scroll_to_top(self): | 169 | def _scroll_to_top(self): |
164 | 160 | if not self.atYBeginning: | 170 | if not self.atYBeginning: |
165 | 161 | 171 | ||
166 | === modified file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_qquicklistview.py' | |||
167 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_qquicklistview.py 2014-04-30 00:46:11 +0000 | |||
168 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_qquicklistview.py 2014-05-20 07:32:50 +0000 | |||
169 | @@ -19,9 +19,7 @@ | |||
170 | 19 | from autopilot import logging as autopilot_logging | 19 | from autopilot import logging as autopilot_logging |
171 | 20 | from autopilot.introspection import dbus | 20 | from autopilot.introspection import dbus |
172 | 21 | 21 | ||
176 | 22 | from ubuntuuitoolkit._custom_proxy_objects import _flickable | 22 | from ubuntuuitoolkit._custom_proxy_objects import _common, _flickable |
174 | 23 | from ubuntuuitoolkit._custom_proxy_objects import _common | ||
175 | 24 | |||
177 | 25 | 23 | ||
178 | 26 | logger = logging.getLogger(__name__) | 24 | logger = logging.getLogger(__name__) |
179 | 27 | 25 | ||
180 | @@ -29,12 +27,16 @@ | |||
181 | 29 | class QQuickListView(_flickable.QQuickFlickable): | 27 | class QQuickListView(_flickable.QQuickFlickable): |
182 | 30 | 28 | ||
183 | 31 | @autopilot_logging.log_action(logger.info) | 29 | @autopilot_logging.log_action(logger.info) |
185 | 32 | def click_element(self, object_name): | 30 | def click_element(self, object_name, direction=None): |
186 | 33 | """Click an element from the list. | 31 | """Click an element from the list. |
187 | 34 | 32 | ||
188 | 35 | It swipes the element into view if it's center is not visible. | 33 | It swipes the element into view if it's center is not visible. |
189 | 36 | 34 | ||
190 | 37 | :parameter objectName: The objectName property of the element to click. | 35 | :parameter objectName: The objectName property of the element to click. |
191 | 36 | :parameter direction: The direction where the element is, it can be | ||
192 | 37 | either 'above' or 'below'. Default value is None, which means we | ||
193 | 38 | don't know where the object is and we will need to search the full | ||
194 | 39 | list. | ||
195 | 38 | 40 | ||
196 | 39 | """ | 41 | """ |
197 | 40 | try: | 42 | try: |
198 | @@ -42,16 +44,31 @@ | |||
199 | 42 | except dbus.StateNotFoundError: | 44 | except dbus.StateNotFoundError: |
200 | 43 | # The element might be on a part of the list that hasn't been | 45 | # The element might be on a part of the list that hasn't been |
201 | 44 | # created yet. We have to search for it scrolling the entire list. | 46 | # created yet. We have to search for it scrolling the entire list. |
203 | 45 | element = self._find_element(object_name) | 47 | element = self._find_element(object_name, direction) |
204 | 46 | self.swipe_child_into_view(element) | 48 | self.swipe_child_into_view(element) |
205 | 47 | self.pointing_device.click_object(element) | 49 | self.pointing_device.click_object(element) |
206 | 48 | 50 | ||
207 | 49 | @autopilot_logging.log_action(logger.info) | 51 | @autopilot_logging.log_action(logger.info) |
213 | 50 | def _find_element(self, object_name): | 52 | def _find_element(self, object_name, direction=None): |
214 | 51 | self._scroll_to_top() | 53 | if direction is None: |
215 | 52 | while not self.atYEnd: | 54 | # We don't know where the object is so we start looking for it from |
216 | 53 | containers = self._get_containers() | 55 | # the top. |
217 | 54 | self._swipe_to_show_more_below(containers) | 56 | self._scroll_to_top() |
218 | 57 | direction = 'below' | ||
219 | 58 | |||
220 | 59 | if direction == 'below': | ||
221 | 60 | fail_condition = lambda: self.atYEnd | ||
222 | 61 | swipe_method = self._swipe_to_show_more_below | ||
223 | 62 | elif direction == 'above': | ||
224 | 63 | fail_condition = lambda: self.atYBeginning | ||
225 | 64 | swipe_method = self._swipe_to_show_more_above | ||
226 | 65 | else: | ||
227 | 66 | raise _common.ToolkitException( | ||
228 | 67 | 'Invalid direction: {}'.format(direction)) | ||
229 | 68 | |||
230 | 69 | containers = self._get_containers() | ||
231 | 70 | while not fail_condition(): | ||
232 | 71 | swipe_method(containers) | ||
233 | 55 | try: | 72 | try: |
234 | 56 | return self.select_single(objectName=object_name) | 73 | return self.select_single(objectName=object_name) |
235 | 57 | except dbus.StateNotFoundError: | 74 | except dbus.StateNotFoundError: |
236 | 58 | 75 | ||
237 | === added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/pickers.py' | |||
238 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/pickers.py 1970-01-01 00:00:00 +0000 | |||
239 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/pickers.py 2014-05-20 07:32:50 +0000 | |||
240 | @@ -0,0 +1,206 @@ | |||
241 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
242 | 2 | # | ||
243 | 3 | # Copyright (C) 2014 Canonical Ltd. | ||
244 | 4 | # | ||
245 | 5 | # This program is free software; you can redistribute it and/or modify | ||
246 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
247 | 7 | # the Free Software Foundation; version 3. | ||
248 | 8 | # | ||
249 | 9 | # This program is distributed in the hope that it will be useful, | ||
250 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
251 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
252 | 12 | # GNU Lesser General Public License for more details. | ||
253 | 13 | # | ||
254 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
255 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
256 | 16 | |||
257 | 17 | import datetime | ||
258 | 18 | import logging | ||
259 | 19 | |||
260 | 20 | from autopilot import logging as autopilot_logging | ||
261 | 21 | from autopilot.introspection import dbus | ||
262 | 22 | |||
263 | 23 | from ubuntuuitoolkit._custom_proxy_objects import ( | ||
264 | 24 | _common, | ||
265 | 25 | _flickable, | ||
266 | 26 | _qquicklistview | ||
267 | 27 | ) | ||
268 | 28 | |||
269 | 29 | |||
270 | 30 | logger = logging.getLogger(__name__) | ||
271 | 31 | |||
272 | 32 | |||
273 | 33 | class DatePicker(_common.UbuntuUIToolkitCustomProxyObjectBase): | ||
274 | 34 | """Autopilot helper for the DatePicker component.""" | ||
275 | 35 | |||
276 | 36 | @autopilot_logging.log_action(logger.info) | ||
277 | 37 | def pick_date(self, date): | ||
278 | 38 | """Pick a date from the date picker. | ||
279 | 39 | |||
280 | 40 | :parameter date: The date to pick. | ||
281 | 41 | :type date: An object with year, month and day attributes, like | ||
282 | 42 | python's datetime.date. | ||
283 | 43 | :raises ubuntuuitoolkit.ToolkitException if the mode of the picker | ||
284 | 44 | doesn't let select a date. | ||
285 | 45 | |||
286 | 46 | """ | ||
287 | 47 | if not self._is_date_picker(): | ||
288 | 48 | raise _common.ToolkitException( | ||
289 | 49 | "Can't pick date. The picker mode is: {!r}.".format(self.mode)) | ||
290 | 50 | if 'Years' in self.mode: | ||
291 | 51 | self._pick_year(date.year) | ||
292 | 52 | self.year.wait_for(date.year) | ||
293 | 53 | if 'Month' in self.mode: | ||
294 | 54 | # Python's date object starts at one. The model in the date picker | ||
295 | 55 | # at 0. | ||
296 | 56 | self._pick_month(date.month - 1) | ||
297 | 57 | self.month.wait_for(date.month - 1) | ||
298 | 58 | if 'Day' in self.mode: | ||
299 | 59 | self._pick_day(date.day) | ||
300 | 60 | self.day.wait_for(date.day) | ||
301 | 61 | |||
302 | 62 | def _is_date_picker(self): | ||
303 | 63 | mode = self.mode | ||
304 | 64 | if 'Years' in mode or 'Months' in mode or 'Days' in mode: | ||
305 | 65 | return True | ||
306 | 66 | else: | ||
307 | 67 | return False | ||
308 | 68 | |||
309 | 69 | @autopilot_logging.log_action(logger.info) | ||
310 | 70 | def _pick_year(self, year): | ||
311 | 71 | picker = self.select_single( | ||
312 | 72 | 'Picker', objectName='PickerRow_YearPicker') | ||
313 | 73 | list_view = picker.select_single( | ||
314 | 74 | _qquicklistview.QQuickListView, objectName='Picker_Linear') | ||
315 | 75 | self._pick_date_value(self.year, year, list_view) | ||
316 | 76 | |||
317 | 77 | @autopilot_logging.log_action(logger.info) | ||
318 | 78 | def _pick_month(self, month): | ||
319 | 79 | picker = self.select_single( | ||
320 | 80 | 'Picker', objectName='PickerRow_MonthPicker') | ||
321 | 81 | path_view = picker.select_single( | ||
322 | 82 | QQuickPathView, objectName='Picker_WrapAround') | ||
323 | 83 | self._pick_date_value(self.month, month, path_view) | ||
324 | 84 | |||
325 | 85 | @autopilot_logging.log_action(logger.info) | ||
326 | 86 | def _pick_day(self, day): | ||
327 | 87 | picker = self.select_single( | ||
328 | 88 | 'Picker', objectName='PickerRow_DayPicker') | ||
329 | 89 | path_view = picker.select_single( | ||
330 | 90 | QQuickPathView, objectName='Picker_WrapAround') | ||
331 | 91 | # Python's date object starts at one. The model in the date picker | ||
332 | 92 | # at 0. | ||
333 | 93 | self._pick_date_value(self.get_date().day - 1, day - 1, path_view) | ||
334 | 94 | |||
335 | 95 | def _pick_date_value(self, current_value, new_value, scrollable): | ||
336 | 96 | if new_value > current_value: | ||
337 | 97 | direction = 'below' | ||
338 | 98 | elif new_value < current_value: | ||
339 | 99 | direction = 'above' | ||
340 | 100 | else: | ||
341 | 101 | logger.debug('The value is already selected.') | ||
342 | 102 | return | ||
343 | 103 | scrollable.click_element( | ||
344 | 104 | object_name='PickerRow_PickerLabel{}'.format(new_value), | ||
345 | 105 | direction=direction) | ||
346 | 106 | |||
347 | 107 | def get_date(self): | ||
348 | 108 | """Return the currently selected date. | ||
349 | 109 | |||
350 | 110 | :return: a python datetime.date object with the selected date. | ||
351 | 111 | |||
352 | 112 | """ | ||
353 | 113 | # Python's date object starts at one. The model in the date picker | ||
354 | 114 | # at 0. | ||
355 | 115 | return datetime.date( | ||
356 | 116 | self.year, self.month + 1, self.day) | ||
357 | 117 | |||
358 | 118 | |||
359 | 119 | class QQuickPathView(_flickable.Scrollable): | ||
360 | 120 | |||
361 | 121 | # TODO make it more general and move it to its own module. | ||
362 | 122 | # --elopio - 2014-05-06 | ||
363 | 123 | |||
364 | 124 | @autopilot_logging.log_action(logger.info) | ||
365 | 125 | def click_element(self, object_name, direction='below'): | ||
366 | 126 | try: | ||
367 | 127 | element = self.select_single(objectName=object_name) | ||
368 | 128 | except dbus.StateNotFoundError: | ||
369 | 129 | # The element might be on a part of the list that hasn't been | ||
370 | 130 | # created yet. We have to search for it scrolling. | ||
371 | 131 | element = self._find_element(object_name, direction) | ||
372 | 132 | self.swipe_child_into_view(element) | ||
373 | 133 | self.pointing_device.click_object(element) | ||
374 | 134 | |||
375 | 135 | @autopilot_logging.log_action(logger.info) | ||
376 | 136 | def _find_element(self, object_name, direction): | ||
377 | 137 | containers = self._get_containers() | ||
378 | 138 | for index in range(self.count): | ||
379 | 139 | if direction == 'below': | ||
380 | 140 | swipe_method = self._swipe_to_show_one_more_below | ||
381 | 141 | elif direction == 'above': | ||
382 | 142 | swipe_method = self._swipe_to_show_one_more_above | ||
383 | 143 | else: | ||
384 | 144 | raise _common.ToolkitException( | ||
385 | 145 | 'Invalid direction: {}'.format(direction)) | ||
386 | 146 | |||
387 | 147 | swipe_method(containers) | ||
388 | 148 | |||
389 | 149 | try: | ||
390 | 150 | return self.select_single(objectName=object_name) | ||
391 | 151 | except dbus.StateNotFoundError: | ||
392 | 152 | pass | ||
393 | 153 | raise _common.ToolkitException( | ||
394 | 154 | 'List element with objectName "{}" not found.'.format(object_name)) | ||
395 | 155 | |||
396 | 156 | @autopilot_logging.log_action(logger.info) | ||
397 | 157 | def _swipe_to_show_one_more_above(self, containers): | ||
398 | 158 | self._swipe_to_show_one_more('above', containers) | ||
399 | 159 | |||
400 | 160 | @autopilot_logging.log_action(logger.info) | ||
401 | 161 | def _swipe_to_show_one_more_below(self, containers): | ||
402 | 162 | self._swipe_to_show_one_more('below', containers) | ||
403 | 163 | |||
404 | 164 | def _swipe_to_show_one_more(self, direction, containers): | ||
405 | 165 | start_x = stop_x = self.globalRect.x + (self.globalRect.width // 2) | ||
406 | 166 | center_y = self.globalRect.y + (self.globalRect.height // 2) | ||
407 | 167 | # XXX This assumes all the children are of the same height | ||
408 | 168 | child = self.get_children_by_type('PickerDelegate')[0] | ||
409 | 169 | top = center_y - (child.globalRect.height // 2) | ||
410 | 170 | bottom = center_y + (child.globalRect.height // 2) | ||
411 | 171 | if direction == 'below': | ||
412 | 172 | start_y = bottom | ||
413 | 173 | stop_y = top | ||
414 | 174 | elif direction == 'above': | ||
415 | 175 | start_y = top | ||
416 | 176 | stop_y = bottom | ||
417 | 177 | else: | ||
418 | 178 | raise _common.ToolkitException( | ||
419 | 179 | 'Invalid direction {}.'.format(direction)) | ||
420 | 180 | self._slow_drag(start_x, stop_x, start_y, stop_y) | ||
421 | 181 | self.dragging.wait_for(False) | ||
422 | 182 | self.moving.wait_for(False) | ||
423 | 183 | |||
424 | 184 | @autopilot_logging.log_action(logger.info) | ||
425 | 185 | def swipe_child_into_view(self, child): | ||
426 | 186 | """Make the child visible. | ||
427 | 187 | |||
428 | 188 | Currently it works only when the object needs to be swiped vertically. | ||
429 | 189 | TODO implement horizontal swiping. --elopio - 2014-03-21 | ||
430 | 190 | |||
431 | 191 | """ | ||
432 | 192 | containers = self._get_containers() | ||
433 | 193 | if not self._is_child_visible(child, containers): | ||
434 | 194 | self._swipe_non_visible_child_into_view(child, containers) | ||
435 | 195 | else: | ||
436 | 196 | logger.debug('The element is already visible.') | ||
437 | 197 | |||
438 | 198 | @autopilot_logging.log_action(logger.info) | ||
439 | 199 | def _swipe_non_visible_child_into_view(self, child, containers): | ||
440 | 200 | while not self._is_child_visible(child, containers): | ||
441 | 201 | # Check the direction of the swipe based on the position of the | ||
442 | 202 | # child relative to the immediate flickable container. | ||
443 | 203 | if child.globalRect.y < self.globalRect.y: | ||
444 | 204 | self._swipe_to_show_one_more_above(containers) | ||
445 | 205 | else: | ||
446 | 206 | self._swipe_to_show_one_more_below(containers) | ||
447 | 0 | 207 | ||
448 | === added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_date_picker.py' | |||
449 | --- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_date_picker.py 1970-01-01 00:00:00 +0000 | |||
450 | +++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_date_picker.py 2014-05-20 07:32:50 +0000 | |||
451 | @@ -0,0 +1,147 @@ | |||
452 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
453 | 2 | # | ||
454 | 3 | # Copyright (C) 2014 Canonical Ltd. | ||
455 | 4 | # | ||
456 | 5 | # This program is free software; you can redistribute it and/or modify | ||
457 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
458 | 7 | # the Free Software Foundation; version 3. | ||
459 | 8 | # | ||
460 | 9 | # This program is distributed in the hope that it will be useful, | ||
461 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
462 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
463 | 12 | # GNU Lesser General Public License for more details. | ||
464 | 13 | # | ||
465 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
466 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
467 | 16 | |||
468 | 17 | import datetime | ||
469 | 18 | |||
470 | 19 | import ubuntuuitoolkit | ||
471 | 20 | from ubuntuuitoolkit import pickers, tests | ||
472 | 21 | |||
473 | 22 | |||
474 | 23 | class DatePickerBaseTestCase(tests.QMLStringAppTestCase): | ||
475 | 24 | |||
476 | 25 | test_qml = (""" | ||
477 | 26 | import QtQuick 2.0 | ||
478 | 27 | import Ubuntu.Components 1.1 | ||
479 | 28 | import Ubuntu.Components.Pickers 1.0 | ||
480 | 29 | |||
481 | 30 | MainView { | ||
482 | 31 | width: units.gu(48) | ||
483 | 32 | height: units.gu(60) | ||
484 | 33 | |||
485 | 34 | Column { | ||
486 | 35 | DatePicker { | ||
487 | 36 | id: datePicker | ||
488 | 37 | objectName: 'datePicker' | ||
489 | 38 | mode: 'Years|Months|Days' | ||
490 | 39 | date: { | ||
491 | 40 | var d = new Date() | ||
492 | 41 | // Make sure that the picker will have higher and lower values | ||
493 | 42 | // to select. | ||
494 | 43 | d.setFullYear(d.getFullYear() + 25) | ||
495 | 44 | d.setMonth('5') | ||
496 | 45 | d.setDate('15') | ||
497 | 46 | return d | ||
498 | 47 | } | ||
499 | 48 | } | ||
500 | 49 | DatePicker { | ||
501 | 50 | id: timePicker | ||
502 | 51 | objectName: 'timePicker' | ||
503 | 52 | mode: 'Hours|Minutes|Seconds' | ||
504 | 53 | } | ||
505 | 54 | } | ||
506 | 55 | } | ||
507 | 56 | """) | ||
508 | 57 | |||
509 | 58 | def setUp(self): | ||
510 | 59 | super(DatePickerBaseTestCase, self).setUp() | ||
511 | 60 | self.date_picker = self.main_view.select_single( | ||
512 | 61 | pickers.DatePicker, objectName='datePicker') | ||
513 | 62 | |||
514 | 63 | |||
515 | 64 | class DatePickerTestCase(DatePickerBaseTestCase): | ||
516 | 65 | |||
517 | 66 | def test_select_date_picker_must_return_custom_proxy_object(self): | ||
518 | 67 | self.assertIsInstance( | ||
519 | 68 | self.date_picker, pickers.DatePicker) | ||
520 | 69 | |||
521 | 70 | def test_pick_date_on_time_picker_must_raise_exception(self): | ||
522 | 71 | time_picker = self.main_view.select_single( | ||
523 | 72 | pickers.DatePicker, objectName='timePicker') | ||
524 | 73 | error = self.assertRaises( | ||
525 | 74 | ubuntuuitoolkit.ToolkitException, time_picker.pick_date, 'dummy') | ||
526 | 75 | self.assertEqual( | ||
527 | 76 | str(error), | ||
528 | 77 | "Can't pick date. The picker mode is: {!r}.".format( | ||
529 | 78 | time_picker.mode)) | ||
530 | 79 | |||
531 | 80 | def test_swipe_to_show_one_more_below_must_select_next_index(self): | ||
532 | 81 | """Test that we don't end up swiping more than needed. | ||
533 | 82 | |||
534 | 83 | This would cause us to miss the element we are looking for, and to have | ||
535 | 84 | to swipe many times in order to finally click it. | ||
536 | 85 | |||
537 | 86 | """ | ||
538 | 87 | picker = self.main_view.select_single( | ||
539 | 88 | 'Picker', objectName='PickerRow_DayPicker') | ||
540 | 89 | path_view = picker.select_single( | ||
541 | 90 | pickers.QQuickPathView, objectName='Picker_WrapAround') | ||
542 | 91 | current_index = path_view.currentIndex | ||
543 | 92 | |||
544 | 93 | path_view._swipe_to_show_one_more_below(path_view._get_containers()) | ||
545 | 94 | |||
546 | 95 | self.assertEqual(path_view.currentIndex, current_index + 1) | ||
547 | 96 | |||
548 | 97 | def test_swipe_to_show_one_more_above_must_select_previous_index(self): | ||
549 | 98 | """Test that we don't end up swiping more than needed. | ||
550 | 99 | |||
551 | 100 | This would cause us to miss the element we are looking for, and to have | ||
552 | 101 | to swipe many times in order to finally click it. | ||
553 | 102 | |||
554 | 103 | """ | ||
555 | 104 | picker = self.main_view.select_single( | ||
556 | 105 | 'Picker', objectName='PickerRow_DayPicker') | ||
557 | 106 | path_view = picker.select_single( | ||
558 | 107 | pickers.QQuickPathView, objectName='Picker_WrapAround') | ||
559 | 108 | current_index = path_view.currentIndex | ||
560 | 109 | |||
561 | 110 | path_view._swipe_to_show_one_more_above(path_view._get_containers()) | ||
562 | 111 | |||
563 | 112 | self.assertEqual(path_view.currentIndex, current_index - 1) | ||
564 | 113 | |||
565 | 114 | |||
566 | 115 | class PickDateFromDatePickerTestCase(DatePickerBaseTestCase): | ||
567 | 116 | |||
568 | 117 | SELECTED_YEAR = datetime.date.today().year + 25 | ||
569 | 118 | SELECTED_MONTH = 6 # June | ||
570 | 119 | SELECTED_DAY = 15 | ||
571 | 120 | |||
572 | 121 | scenarios = [ | ||
573 | 122 | ('higher year', { | ||
574 | 123 | 'date_to_pick': datetime.date( | ||
575 | 124 | SELECTED_YEAR + 10, SELECTED_MONTH, SELECTED_DAY)}), | ||
576 | 125 | ('lower year', { | ||
577 | 126 | 'date_to_pick': datetime.date( | ||
578 | 127 | SELECTED_YEAR - 10, SELECTED_MONTH, SELECTED_DAY)}), | ||
579 | 128 | ('higher month', { | ||
580 | 129 | 'date_to_pick': datetime.date( | ||
581 | 130 | SELECTED_YEAR, SELECTED_MONTH + 4, SELECTED_DAY)}), | ||
582 | 131 | ('lower month', { | ||
583 | 132 | 'date_to_pick': datetime.date( | ||
584 | 133 | SELECTED_YEAR, SELECTED_MONTH - 4, SELECTED_DAY)}), | ||
585 | 134 | ('higher day', { | ||
586 | 135 | 'date_to_pick': datetime.date( | ||
587 | 136 | SELECTED_YEAR, SELECTED_MONTH, SELECTED_DAY + 10)}), | ||
588 | 137 | ('lower day', { | ||
589 | 138 | 'date_to_pick': datetime.date( | ||
590 | 139 | SELECTED_YEAR, SELECTED_MONTH, SELECTED_DAY - 10)}), | ||
591 | 140 | ('change all value', { | ||
592 | 141 | 'date_to_pick': datetime.date( | ||
593 | 142 | SELECTED_YEAR - 10, SELECTED_MONTH + 4, SELECTED_DAY - 10)}), | ||
594 | 143 | ] | ||
595 | 144 | |||
596 | 145 | def test_pick_date(self): | ||
597 | 146 | self.date_picker.pick_date(self.date_to_pick) | ||
598 | 147 | self.assertEqual(self.date_picker.get_date(), self.date_to_pick) | ||
599 | 0 | 148 | ||
600 | === modified file 'tests/unit_x11/tst_components/tst_datepicker.qml' | |||
601 | --- tests/unit_x11/tst_components/tst_datepicker.qml 2014-05-05 17:45:45 +0000 | |||
602 | +++ tests/unit_x11/tst_components/tst_datepicker.qml 2014-05-20 07:32:50 +0000 | |||
603 | @@ -59,7 +59,7 @@ | |||
604 | 59 | function getPickerLabel(picker, name) { | 59 | function getPickerLabel(picker, name) { |
605 | 60 | var pickerItem = findChild(picker, name); | 60 | var pickerItem = findChild(picker, name); |
606 | 61 | var pickerCurrent = findChild(pickerItem, "Picker_ViewLoader"); | 61 | var pickerCurrent = findChild(pickerItem, "Picker_ViewLoader"); |
608 | 62 | return findChild(pickerCurrent.item.currentItem, "PickerRow_PickerLabel"); | 62 | return pickerCurrent.item.currentItem.children[2]; |
609 | 63 | } | 63 | } |
610 | 64 | function getPickerModel(picker, name) { | 64 | function getPickerModel(picker, name) { |
611 | 65 | var pickerItem = findInvisibleChild(picker, name); | 65 | var pickerItem = findInvisibleChild(picker, name); |
Ok I think understand this change, appears thoroughly tested, nice work :) . I'm assuming that the comment on l.535 doesn't apply to the present merge proposal.