Merge lp:~nataliabidart/ubuntuone-control-panel/unify-loading-messages into lp:ubuntuone-control-panel
- unify-loading-messages
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Natalia Bidart |
Approved revision: | 44 |
Merged at revision: | 39 |
Proposed branch: | lp:~nataliabidart/ubuntuone-control-panel/unify-loading-messages |
Merge into: | lp:ubuntuone-control-panel |
Prerequisite: | lp:~nataliabidart/ubuntuone-control-panel/remove-devices |
Diff against target: |
1267 lines (+423/-249) 10 files modified
data/account.ui (+92/-51) data/controlpanel.ui (+0/-17) data/folders.ui (+1/-14) data/management.ui (+16/-1) ubuntuone/controlpanel/backend.py (+10/-1) ubuntuone/controlpanel/gtk/gui.py (+105/-70) ubuntuone/controlpanel/gtk/tests/test_gui.py (+152/-72) ubuntuone/controlpanel/gtk/tests/test_widgets.py (+6/-13) ubuntuone/controlpanel/gtk/widgets.py (+15/-8) ubuntuone/controlpanel/tests/test_backend.py (+26/-2) |
To merge this branch: | bzr merge lp:~nataliabidart/ubuntuone-control-panel/unify-loading-messages |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
John Lenton (community) | Approve | ||
Roberto Alsina (community) | natty fieldtest | Approve | |
Review via email: mp+44147@code.launchpad.net |
Commit message
* Updated list of messages to be shown on the overview panel (LP: #690379).
* Implemented callback for failure when loading devices.
* Added a spinner on every UbuntuOneBin to be shown while loading the panel content.
Description of the change
To run the tests, do:
./run-tests
To test IRL, open 2 terminals pointing to this branch, and run:
terminal 1: DEBUG=True PYTHONPATH=. ./bin/ubuntuone
terminal 2: DEBUG=True PYTHONPATH=. ./bin/ubuntuone
and play with all the tabs except the 4th tab (not implemented yet).
Roberto Alsina (ralsina) wrote : | # |
- 41. By Natalia Bidart
-
UI fixes: progress bar is narrow now, less bold on Account tab, max size is
juts a size request. - 42. By Natalia Bidart
-
Data within account section is now indented to the right.
- 43. By Natalia Bidart
-
12px indentation for account labels.
John Lenton (chipaca) wrote : | # |
I like it better now. Still needs UI love, but at least this will probably let the UI reviewer survive long enough to give feedback :)
Martin Albisetti (beuno) wrote : | # |
This is a lot, and almost everything is unrelated, so I'm not really voting :)
Header:
- When people have a lot of storage, and little used, the quota bar is basically "empty". I suggest setting the minimum to display to 5% or something noticeable
- All my files are up to date, and the text on the top-right seems to long and too shy at telling me. Maybe something shorter like "All files up-to-date", with a green tick?
Account tab:
- I would change the "Upgrade subscription" and "Support options" from links to something that makes it clearer that it will launch a browser. Maybe a small generic browser icon? Maybe an arrow at the end, hinting that you'll get taken outside of the app?
- The "upgrade subscription" could be placed underneath the
"Account type" block, as that's what you would be upgrading. It should probably be called something else, like "Buy more storage and new plans"
- "Account type" title seems leak a bit too much of our implementation lingo, maybe "Account information"?
- I think I would drop the "Name" section, and instead place that above where it says "Welcome to Ubuntu One!", like the dashboard: "Welcome to Ubuntu One, Martin Albisetti"
- I'd rename support options to "Get support"
Folders tab:
- I wouldn't center the contents, it makes it a bit harder to predict where content will be, as it moves around. I would instead top-left align it
- I'd rename "Subscribed" to "Is syncing", as "subscribed" is too much of an implementation detail
- The text on the title of this tab is too long and wraps. Maked it look awkward. It coulb be re-written to something like: Folders being watched by Ubuntu One (you don't need to clarify what "Subscribed" means if you address the above point
- The padding on the right is also different that on the left (ie, non-existent)
Devices tab:
- I love that you now cache the account information :)
- The spinner says "Loading", maybe it should say "Updating", as you are already presented with information?
- Again, I would top-left align the content
- I'd add some padding to the "Limit bandwidth" section, to split it out of the rest of the content
- Maybe the remove button could be a bit more informative. Not sure what text would be best, but maybe "Stop from syncing"? That's not great text, lets think about it.
Natalia Bidart (nataliabidart) wrote : | # |
Martin, great feedback! really great. Thanks, I'll be addressing this in future branches.
- 44. By Natalia Bidart
-
Link buttons now work, though the linkbutotn on the overview panel is
triggering:GtkWarning: Unable to show '': Operation not supported
since the uri is set to ''.
Preview Diff
1 | === modified file 'data/account.ui' |
2 | --- data/account.ui 2010-11-30 20:13:10 +0000 |
3 | +++ data/account.ui 2010-12-20 21:58:42 +0000 |
4 | @@ -9,26 +9,107 @@ |
5 | <child> |
6 | <object class="GtkHBox" id="hbox1"> |
7 | <property name="visible">True</property> |
8 | - <property name="spacing">15</property> |
9 | + <property name="spacing">10</property> |
10 | <child> |
11 | - <object class="GtkVBox" id="vbox1"> |
12 | + <object class="GtkVBox" id="account"> |
13 | <property name="visible">True</property> |
14 | <property name="spacing">10</property> |
15 | <child> |
16 | - <object class="GtkHBox" id="name_box"> |
17 | + <object class="GtkVBox" id="data"> |
18 | <property name="visible">True</property> |
19 | - <property name="spacing">5</property> |
20 | + <property name="spacing">10</property> |
21 | <child> |
22 | - <object class="GtkLabel" id="name_label"> |
23 | + <object class="GtkVBox" id="vbox1"> |
24 | <property name="visible">True</property> |
25 | - <property name="xalign">0</property> |
26 | - <property name="label">Name:</property> |
27 | + <child> |
28 | + <object class="GtkLabel" id="label1"> |
29 | + <property name="visible">True</property> |
30 | + <property name="xalign">0</property> |
31 | + <property name="label" translatable="yes"><b>Name:</b></property> |
32 | + <property name="use_markup">True</property> |
33 | + </object> |
34 | + <packing> |
35 | + <property name="position">0</property> |
36 | + </packing> |
37 | + </child> |
38 | + <child> |
39 | + <object class="GtkLabel" id="name_label"> |
40 | + <property name="visible">True</property> |
41 | + <property name="xalign">0</property> |
42 | + <property name="xpad">12</property> |
43 | + <property name="label">tester name</property> |
44 | + <property name="use_markup">True</property> |
45 | + </object> |
46 | + <packing> |
47 | + <property name="expand">False</property> |
48 | + <property name="position">1</property> |
49 | + </packing> |
50 | + </child> |
51 | </object> |
52 | <packing> |
53 | - <property name="expand">False</property> |
54 | <property name="position">0</property> |
55 | </packing> |
56 | </child> |
57 | + <child> |
58 | + <object class="GtkVBox" id="vbox2"> |
59 | + <property name="visible">True</property> |
60 | + <child> |
61 | + <object class="GtkLabel" id="label3"> |
62 | + <property name="visible">True</property> |
63 | + <property name="xalign">0</property> |
64 | + <property name="label" translatable="yes"><b>Account type:</b></property> |
65 | + <property name="use_markup">True</property> |
66 | + </object> |
67 | + <packing> |
68 | + <property name="position">0</property> |
69 | + </packing> |
70 | + </child> |
71 | + <child> |
72 | + <object class="GtkLabel" id="type_label"> |
73 | + <property name="visible">True</property> |
74 | + <property name="xalign">0</property> |
75 | + <property name="xpad">12</property> |
76 | + <property name="label">22GB awesomeness</property> |
77 | + </object> |
78 | + <packing> |
79 | + <property name="position">1</property> |
80 | + </packing> |
81 | + </child> |
82 | + </object> |
83 | + <packing> |
84 | + <property name="position">1</property> |
85 | + </packing> |
86 | + </child> |
87 | + <child> |
88 | + <object class="GtkVBox" id="vbox3"> |
89 | + <property name="visible">True</property> |
90 | + <child> |
91 | + <object class="GtkLabel" id="label2"> |
92 | + <property name="visible">True</property> |
93 | + <property name="xalign">0</property> |
94 | + <property name="label" translatable="yes"><b>Email address:</b></property> |
95 | + <property name="use_markup">True</property> |
96 | + </object> |
97 | + <packing> |
98 | + <property name="position">0</property> |
99 | + </packing> |
100 | + </child> |
101 | + <child> |
102 | + <object class="GtkLabel" id="email_label"> |
103 | + <property name="visible">True</property> |
104 | + <property name="xalign">0</property> |
105 | + <property name="xpad">12</property> |
106 | + <property name="label">a@example.com</property> |
107 | + </object> |
108 | + <packing> |
109 | + <property name="position">1</property> |
110 | + </packing> |
111 | + </child> |
112 | + </object> |
113 | + <packing> |
114 | + <property name="position">2</property> |
115 | + </packing> |
116 | + </child> |
117 | </object> |
118 | <packing> |
119 | <property name="expand">False</property> |
120 | @@ -36,48 +117,6 @@ |
121 | </packing> |
122 | </child> |
123 | <child> |
124 | - <object class="GtkHBox" id="type_box"> |
125 | - <property name="visible">True</property> |
126 | - <property name="spacing">5</property> |
127 | - <child> |
128 | - <object class="GtkLabel" id="label2"> |
129 | - <property name="visible">True</property> |
130 | - <property name="xalign">0</property> |
131 | - <property name="label" translatable="yes">Account type:</property> |
132 | - </object> |
133 | - <packing> |
134 | - <property name="expand">False</property> |
135 | - <property name="position">0</property> |
136 | - </packing> |
137 | - </child> |
138 | - </object> |
139 | - <packing> |
140 | - <property name="expand">False</property> |
141 | - <property name="position">1</property> |
142 | - </packing> |
143 | - </child> |
144 | - <child> |
145 | - <object class="GtkHBox" id="email_box"> |
146 | - <property name="visible">True</property> |
147 | - <property name="spacing">5</property> |
148 | - <child> |
149 | - <object class="GtkLabel" id="label3"> |
150 | - <property name="visible">True</property> |
151 | - <property name="xalign">0</property> |
152 | - <property name="label" translatable="yes">Email address:</property> |
153 | - </object> |
154 | - <packing> |
155 | - <property name="expand">False</property> |
156 | - <property name="position">0</property> |
157 | - </packing> |
158 | - </child> |
159 | - </object> |
160 | - <packing> |
161 | - <property name="expand">False</property> |
162 | - <property name="position">2</property> |
163 | - </packing> |
164 | - </child> |
165 | - <child> |
166 | <object class="GtkHButtonBox" id="hbuttonbox1"> |
167 | <property name="layout_style">center</property> |
168 | <child> |
169 | @@ -97,7 +136,7 @@ |
170 | <packing> |
171 | <property name="expand">False</property> |
172 | <property name="pack_type">end</property> |
173 | - <property name="position">3</property> |
174 | + <property name="position">1</property> |
175 | </packing> |
176 | </child> |
177 | </object> |
178 | @@ -108,6 +147,7 @@ |
179 | <child> |
180 | <object class="GtkVButtonBox" id="vbuttonbox1"> |
181 | <property name="visible">True</property> |
182 | + <property name="spacing">10</property> |
183 | <property name="layout_style">center</property> |
184 | <child> |
185 | <object class="GtkLinkButton" id="linkbutton1"> |
186 | @@ -144,6 +184,7 @@ |
187 | </object> |
188 | <packing> |
189 | <property name="expand">False</property> |
190 | + <property name="pack_type">end</property> |
191 | <property name="position">1</property> |
192 | </packing> |
193 | </child> |
194 | |
195 | === removed file 'data/controlpanel.ui' |
196 | --- data/controlpanel.ui 2010-09-17 22:23:25 +0000 |
197 | +++ data/controlpanel.ui 1970-01-01 00:00:00 +0000 |
198 | @@ -1,17 +0,0 @@ |
199 | -<?xml version="1.0" encoding="UTF-8"?> |
200 | -<interface> |
201 | - <requires lib="gtk+" version="2.16"/> |
202 | - <!-- interface-naming-policy project-wide --> |
203 | - <object class="GtkVBox" id="itself"> |
204 | - <property name="visible">True</property> |
205 | - <child> |
206 | - <placeholder/> |
207 | - </child> |
208 | - <child> |
209 | - <placeholder/> |
210 | - </child> |
211 | - <child> |
212 | - <placeholder/> |
213 | - </child> |
214 | - </object> |
215 | -</interface> |
216 | |
217 | === modified file 'data/folders.ui' |
218 | --- data/folders.ui 2010-12-16 15:14:22 +0000 |
219 | +++ data/folders.ui 2010-12-20 21:58:42 +0000 |
220 | @@ -7,19 +7,6 @@ |
221 | <property name="border_width">10</property> |
222 | <property name="spacing">10</property> |
223 | <child> |
224 | - <object class="GtkAlignment" id="label_alignment"> |
225 | - <property name="xscale">0</property> |
226 | - <property name="yscale">0</property> |
227 | - <child> |
228 | - <placeholder/> |
229 | - </child> |
230 | - </object> |
231 | - <packing> |
232 | - <property name="expand">False</property> |
233 | - <property name="position">0</property> |
234 | - </packing> |
235 | - </child> |
236 | - <child> |
237 | <object class="GtkScrolledWindow" id="scrolledwindow1"> |
238 | <property name="visible">True</property> |
239 | <property name="can_focus">True</property> |
240 | @@ -44,7 +31,7 @@ |
241 | </child> |
242 | </object> |
243 | <packing> |
244 | - <property name="position">1</property> |
245 | + <property name="position">0</property> |
246 | </packing> |
247 | </child> |
248 | </object> |
249 | |
250 | === modified file 'data/management.ui' |
251 | --- data/management.ui 2010-11-30 17:21:15 +0000 |
252 | +++ data/management.ui 2010-12-20 21:58:42 +0000 |
253 | @@ -16,8 +16,23 @@ |
254 | <property name="border_width">10</property> |
255 | <property name="spacing">10</property> |
256 | <child> |
257 | - <object class="GtkProgressBar" id="quota_progressbar"> |
258 | + <object class="GtkHBox" id="quota_box"> |
259 | <property name="visible">True</property> |
260 | + <child> |
261 | + <object class="GtkAlignment" id="alignment1"> |
262 | + <property name="visible">True</property> |
263 | + <property name="xscale">0</property> |
264 | + <property name="yscale">0</property> |
265 | + <child> |
266 | + <object class="GtkProgressBar" id="quota_progressbar"> |
267 | + <property name="visible">True</property> |
268 | + </object> |
269 | + </child> |
270 | + </object> |
271 | + <packing> |
272 | + <property name="position">0</property> |
273 | + </packing> |
274 | + </child> |
275 | </object> |
276 | <packing> |
277 | <property name="expand">False</property> |
278 | |
279 | === modified file 'ubuntuone/controlpanel/backend.py' |
280 | --- ubuntuone/controlpanel/backend.py 2010-12-18 17:35:52 +0000 |
281 | +++ ubuntuone/controlpanel/backend.py 2010-12-20 21:58:42 +0000 |
282 | @@ -145,7 +145,16 @@ |
283 | result = {} |
284 | |
285 | account_info = yield self.wc.call_api(ACCOUNT_API) |
286 | - result["type"] = account_info["subscription"]["description"] |
287 | + |
288 | + if "current_plan" in account_info: |
289 | + desc = account_info["current_plan"] |
290 | + elif "subscription" in account_info \ |
291 | + and "description" in account_info["subscription"]: |
292 | + desc = account_info["subscription"]["description"] |
293 | + else: |
294 | + desc = '' |
295 | + |
296 | + result["type"] = desc |
297 | result["name"] = account_info["nickname"] |
298 | result["email"] = account_info["email"] |
299 | |
300 | |
301 | === modified file 'ubuntuone/controlpanel/gtk/gui.py' |
302 | --- ubuntuone/controlpanel/gtk/gui.py 2010-12-18 17:16:35 +0000 |
303 | +++ ubuntuone/controlpanel/gtk/gui.py 2010-12-20 21:58:42 +0000 |
304 | @@ -61,15 +61,20 @@ |
305 | ORANGE = '#c95724' |
306 | LOADING = _('Loading...') |
307 | OVERVIEW_MSGS = [ |
308 | - _('<b>Ubuntu One</b> Music Store. ' |
309 | - 'A cloud enabled mobile shopping experience.\n' |
310 | - '<small>«this is custom text #1»</small>'), |
311 | - _('<i>Mobile</i> contacts sync. ' |
312 | - '<span foreground="red">Your portable address book.</span>\n' |
313 | - '<small>«this is custom text #3»</small>'), |
314 | - _('...'), |
315 | - _('Sync data between computers.\n' |
316 | - '<small>«this is custom text #<i>N</i>»</small>')] |
317 | + _('Backup and access your files from <b>any</b> computer (Ubuntu & ' |
318 | + 'Windows), mobile device (Android) or the web.'), |
319 | + _('A <b>portable address book</b> that unifies your most important ' |
320 | + 'contact sources: mobile phones, social networks, email clients and ' |
321 | + 'services.'), |
322 | + _('A <b>personal music library</b> that can be streamed to Android, ' |
323 | + 'iPhone and AirPlay-enabled devices.'), |
324 | + _('Add to that music library with a <b>Music Store</b> that automatically ' |
325 | + 'delivers to your personal cloud and connected devices.'), |
326 | + _('Keep your <b>Firefox bookmarks</b> and <b>Tomboy notes</b> in sync ' |
327 | + 'across computers (Ubuntu & Windows).'), |
328 | + _('<b>2GB</b> of free storage.'), |
329 | +] |
330 | +VALUE_ERROR = _('Value could not be retrieved.') |
331 | WARNING_MARKUP = '<span foreground="%s"><b>%%s</b></span>' % ORANGE |
332 | KILOBYTES = 1024 |
333 | |
334 | @@ -92,8 +97,6 @@ |
335 | class ControlPanelMixin(object): |
336 | """The main interface for the Ubuntu One control panel.""" |
337 | |
338 | - VALUE_ERROR = _('Value could not be retrieved.') |
339 | - |
340 | def __init__(self, filename=None): |
341 | bus = dbus.SessionBus() |
342 | try: |
343 | @@ -154,7 +157,7 @@ |
344 | self.set_title(self.TITLE % {'app_name': U1_APP_NAME}) |
345 | self.set_position(gtk.WIN_POS_CENTER_ALWAYS) |
346 | self.set_icon_name('ubuntuone') |
347 | - self.set_geometry_hints(max_width=736, max_height=525) # bug #683164 |
348 | + self.set_size_request(-1, 525) # bug #683164 |
349 | |
350 | self.connect('delete-event', lambda w, e: gtk.main_quit()) |
351 | self.show() |
352 | @@ -171,46 +174,49 @@ |
353 | gtk.main() |
354 | |
355 | |
356 | -class ControlPanel(gtk.VBox, ControlPanelMixin): |
357 | +class ControlPanel(gtk.Notebook): |
358 | """The control panel per se, can be added into any other widget.""" |
359 | |
360 | # should not be any larger than 736x525 |
361 | |
362 | def __init__(self, window_id=0): |
363 | - gtk.VBox.__init__(self) |
364 | - ControlPanelMixin.__init__(self, filename='controlpanel.ui') |
365 | + gtk.Notebook.__init__(self) |
366 | self._window_id = window_id |
367 | |
368 | - self.management = None |
369 | - self.overview = None |
370 | + self.set_show_tabs(False) |
371 | + self.set_show_border(False) |
372 | + |
373 | + self.overview = OverviewPanel(window_id=self._window_id) |
374 | + self.insert_page(self.overview, position=0) |
375 | + |
376 | + self.management = ManagementPanel() |
377 | + self.insert_page(self.management, position=1) |
378 | + |
379 | + self.overview.connect('credentials-found', |
380 | + self.on_show_management_panel) |
381 | + self.management.connect('local-device-removed', |
382 | + self.on_show_overview_panel) |
383 | + |
384 | + self.show() |
385 | self.on_show_overview_panel() |
386 | - self.show() |
387 | + |
388 | + logger.debug('%s: started (window size %r).', |
389 | + self.__class__.__name__, self.get_size_request()) |
390 | |
391 | def on_show_overview_panel(self, widget=None): |
392 | """Show the overview panel.""" |
393 | - for child in self.get_children(): |
394 | - self.remove(child) |
395 | - |
396 | - self.overview = OverviewPanel(window_id=self._window_id) |
397 | - self.overview.connect('credentials-found', |
398 | - self.on_show_management_panel) |
399 | - self.add(self.overview) |
400 | + self.set_current_page(0) |
401 | |
402 | def on_show_management_panel(self, widget=None, |
403 | credentials_are_new=False, token=None): |
404 | """Show the notebook (main panel).""" |
405 | - if self.overview in self.get_children(): |
406 | - self.remove(self.overview) |
407 | - |
408 | - self.management = ManagementPanel() |
409 | + if self.get_current_page() == 0: |
410 | + self.management.load() |
411 | if credentials_are_new: |
412 | # redirect user to Folders page to review folders subscription |
413 | self.management.folders_button.clicked() |
414 | |
415 | - self.add(self.management) |
416 | - |
417 | - self.management.connect('local-device-removed', |
418 | - self.on_show_overview_panel) |
419 | + self.next_page() |
420 | |
421 | |
422 | class UbuntuOneBin(gtk.VBox): |
423 | @@ -226,8 +232,26 @@ |
424 | self.title = PanelTitle(markup='<b>' + title + '</b>') |
425 | self.pack_start(self.title, expand=False) |
426 | |
427 | + self.message = LabelLoading(LOADING) |
428 | + self.pack_start(self.message, expand=False) |
429 | + |
430 | self.show_all() |
431 | |
432 | + @log_call(logger.debug) |
433 | + def on_success(self, message=''): |
434 | + """Use this callback to stop the Loading and show 'message'.""" |
435 | + self.message.stop() |
436 | + self.message.set_markup(message) |
437 | + |
438 | + @log_call(logger.error) |
439 | + def on_error(self, message=None): |
440 | + """Use this callback to stop the Loading and set a warning message.""" |
441 | + if message == None: |
442 | + message = VALUE_ERROR |
443 | + |
444 | + self.message.stop() |
445 | + self.message.set_markup(WARNING_MARKUP % message) |
446 | + |
447 | |
448 | class OverviewPanel(GreyableBin, ControlPanelMixin): |
449 | """The overview panel. Introduces Ubuntu One to the not logged user.""" |
450 | @@ -244,7 +268,6 @@ |
451 | BULLET = '<span foreground="%s">✔</span>' % ORANGE |
452 | |
453 | def __init__(self, messages=None, window_id=0): |
454 | - gtk.link_button_set_uri_hook(lambda *a: None) |
455 | GreyableBin.__init__(self) |
456 | ControlPanelMixin.__init__(self, filename='overview.ui') |
457 | self.add(self.itself) |
458 | @@ -252,6 +275,8 @@ |
459 | self.warning_label.set_property('xalign', 0.5) |
460 | self.warning_label.connect('size-allocate', self.on_size_allocate) |
461 | |
462 | + self.connect_button.set_uri('') |
463 | + |
464 | self._credentials_are_new = False |
465 | self._messages = messages |
466 | self._window_id = window_id |
467 | @@ -343,8 +368,8 @@ |
468 | @log_call(logger.info) |
469 | def on_credentials_found(self, app_name, credentials): |
470 | """SSO backend notifies of credentials found.""" |
471 | + self.set_property('greyed', False) |
472 | self.emit('credentials-found', self._credentials_are_new, credentials) |
473 | - self.hide() |
474 | |
475 | @filter_by_app_name |
476 | @log_call(logger.info) |
477 | @@ -389,6 +414,9 @@ |
478 | """The account panel. The user can manage the subscription.""" |
479 | |
480 | TITLE = _('Welcome to Ubuntu One!') |
481 | + NAME = _('Name') |
482 | + TYPE = _('Account type') |
483 | + EMAIL = _('Email address') |
484 | |
485 | def __init__(self): |
486 | UbuntuOneBin.__init__(self) |
487 | @@ -400,31 +428,22 @@ |
488 | self.on_account_info_ready) |
489 | self.backend.connect_to_signal('AccountInfoError', |
490 | self.on_account_info_error) |
491 | - |
492 | - self.name_label = LabelLoading(LOADING) |
493 | - self.name_box.pack_start(self.name_label) |
494 | - |
495 | - self.type_label = LabelLoading(LOADING) |
496 | - self.type_box.pack_start(self.type_label) |
497 | - |
498 | - self.email_label = LabelLoading(LOADING) |
499 | - self.email_box.pack_start(self.email_label) |
500 | + self.account.hide() |
501 | |
502 | @log_call(logger.debug) |
503 | def on_account_info_ready(self, info): |
504 | """Backend notifies of account info.""" |
505 | - for i in ('name', 'type', 'email'): |
506 | + self.on_success() |
507 | + |
508 | + for i in (u'name', u'type', u'email'): |
509 | label = getattr(self, '%s_label' % i) |
510 | - label.set_markup('<b>%s</b>' % info[i]) |
511 | - label.stop() |
512 | + label.set_markup('%s' % (info[i])) |
513 | + self.account.show() |
514 | |
515 | @log_call(logger.error) |
516 | def on_account_info_error(self, error_dict=None): |
517 | """Backend notifies of an error when fetching account info.""" |
518 | - for i in ('name', 'type', 'email'): |
519 | - label = getattr(self, '%s_label' % i) |
520 | - label.set_markup(WARNING_MARKUP % self.VALUE_ERROR) |
521 | - label.stop() |
522 | + self.on_error() |
523 | |
524 | |
525 | class FoldersPanel(UbuntuOneBin, ControlPanelMixin): |
526 | @@ -445,24 +464,20 @@ |
527 | self.backend.connect_to_signal('VolumesInfoError', |
528 | self.on_volumes_info_error) |
529 | self.volumes = None |
530 | - self.volumes_label = LabelLoading(LOADING) |
531 | - self.label_alignment.add(self.volumes_label) |
532 | self._subscribed = [] |
533 | |
534 | def on_volumes_info_ready(self, info): |
535 | """Backend notifies of volumes info.""" |
536 | - self.volumes_label.stop() |
537 | |
538 | if self.volumes is not None: |
539 | self.folders.remove(self.volumes) |
540 | self.volumes = None |
541 | |
542 | if not info: |
543 | - self.volumes_label.set_markup(self.NO_VOLUMES) |
544 | - self.label_alignment.show() |
545 | + self.on_success(self.NO_VOLUMES) |
546 | return |
547 | else: |
548 | - self.label_alignment.hide() |
549 | + self.on_success() |
550 | |
551 | self.volumes = gtk.Table(rows=len(info) + 1, columns=2) |
552 | |
553 | @@ -493,9 +508,7 @@ |
554 | @log_call(logger.error) |
555 | def on_volumes_info_error(self, error_dict=None): |
556 | """Backend notifies of an error when fetching volumes info.""" |
557 | - self.volumes_label.stop() |
558 | - self._set_warning(self.VALUE_ERROR, self.volumes_label) |
559 | - self.label_alignment.show() |
560 | + self.on_error() |
561 | |
562 | def on_subscribed_clicked(self, checkbutton): |
563 | """The user toggled 'checkbutton'.""" |
564 | @@ -507,7 +520,7 @@ |
565 | def load(self): |
566 | """Load the volume list.""" |
567 | self.backend.volumes_info() |
568 | - self.volumes_label.start() |
569 | + self.message.start() |
570 | |
571 | |
572 | class Device(gtk.VBox, ControlPanelMixin): |
573 | @@ -680,6 +693,7 @@ |
574 | |
575 | TITLE = _('The devices connected with your personal cloud network are ' |
576 | 'listed below:') |
577 | + NO_DEVICES = _('No devices to show.') |
578 | |
579 | def __init__(self): |
580 | UbuntuOneBin.__init__(self) |
581 | @@ -695,10 +709,17 @@ |
582 | self.on_devices_info_error) |
583 | self.backend.connect_to_signal('DeviceRemoved', |
584 | self.on_device_removed) |
585 | - self.backend.devices_info() |
586 | |
587 | def on_devices_info_ready(self, info): |
588 | """Backend notifies of devices info.""" |
589 | + for child in self.devices.get_children(): |
590 | + self.devices.remove(child) |
591 | + |
592 | + if not info: |
593 | + self.on_success(self.NO_DEVICES) |
594 | + else: |
595 | + self.on_success() |
596 | + |
597 | for device_info in info: |
598 | device = Device() |
599 | device_info['device_name'] = device_info.pop('name', '') |
600 | @@ -711,6 +732,7 @@ |
601 | @log_call(logger.error) |
602 | def on_devices_info_error(self, error_dict=None): |
603 | """Backend notifies of an error when fetching volumes info.""" |
604 | + self.on_error() |
605 | |
606 | @log_call(logger.warning) |
607 | def on_device_removed(self, device_id): |
608 | @@ -722,6 +744,11 @@ |
609 | if child.is_local: |
610 | self.emit('local-device-removed') |
611 | |
612 | + def load(self): |
613 | + """Load the device list.""" |
614 | + self.backend.devices_info() |
615 | + self.message.start() |
616 | + |
617 | |
618 | class ApplicationsPanel(UbuntuOneBin, ControlPanelMixin): |
619 | """The applications panel.""" |
620 | @@ -735,6 +762,9 @@ |
621 | self.add(self.itself) |
622 | self.show() |
623 | |
624 | + self.message.stop() |
625 | + self.message.set_text('Under construction') |
626 | + |
627 | |
628 | class ManagementPanel(gtk.VBox, ControlPanelMixin): |
629 | """The management panel. |
630 | @@ -780,9 +810,11 @@ |
631 | self.on_file_sync_status_error) |
632 | |
633 | self.header.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(DEFAULT_BG)) |
634 | + |
635 | self.quota_label = LabelLoading(LOADING, fg_color=DEFAULT_FG) |
636 | + self.quota_box.pack_start(self.quota_label, expand=False) |
637 | + |
638 | self.status_label = LabelLoading(LOADING, fg_color=DEFAULT_FG) |
639 | - self.status_box.pack_start(self.quota_label, expand=False) |
640 | self.status_box.pack_end(self.status_label, expand=False) |
641 | |
642 | self.account = AccountPanel() |
643 | @@ -802,12 +834,9 @@ |
644 | self.notebook.insert_page(getattr(self, tab), position=page_num) |
645 | |
646 | self.folders_button.connect('clicked', lambda b: self.folders.load()) |
647 | - sig = 'local-device-removed' |
648 | - self.devices.connect(sig, lambda widget: self.emit(sig)) |
649 | - |
650 | - self.backend.account_info() |
651 | - self.backend.file_sync_status() |
652 | - self.account_button.clicked() |
653 | + self.devices_button.connect('clicked', lambda b: self.devices.load()) |
654 | + self.devices.connect('local-device-removed', |
655 | + lambda widget: self.emit('local-device-removed')) |
656 | |
657 | def _update_quota(self, msg, data=None): |
658 | """Update the quota info.""" |
659 | @@ -824,6 +853,12 @@ |
660 | self.status_label.set_markup(msg) |
661 | self.status_label.stop() |
662 | |
663 | + def load(self): |
664 | + """Load the account info and file sync status list.""" |
665 | + self.backend.account_info() |
666 | + self.backend.file_sync_status() |
667 | + self.account_button.clicked() |
668 | + |
669 | @log_call(logger.debug) |
670 | def on_account_info_ready(self, info): |
671 | """Backend notifies of account info.""" |
672 | @@ -836,7 +871,7 @@ |
673 | @log_call(logger.error) |
674 | def on_account_info_error(self, error_dict=None): |
675 | """Backend notifies of an error when fetching account info.""" |
676 | - self._update_quota(WARNING_MARKUP % self.VALUE_ERROR) |
677 | + self._update_quota(WARNING_MARKUP % VALUE_ERROR) |
678 | |
679 | @log_call(logger.info) |
680 | def on_file_sync_status_disabled(self, msg): |
681 | |
682 | === modified file 'ubuntuone/controlpanel/gtk/tests/test_gui.py' |
683 | --- ubuntuone/controlpanel/gtk/tests/test_gui.py 2010-12-18 16:21:36 +0000 |
684 | +++ ubuntuone/controlpanel/gtk/tests/test_gui.py 2010-12-20 21:58:42 +0000 |
685 | @@ -215,7 +215,6 @@ |
686 | def assert_warning_correct(self, warning, text): |
687 | """Check that 'warning' is visible, showing 'text'.""" |
688 | self.assertTrue(warning.get_visible(), 'Must be visible.') |
689 | - self.assertEqual(warning.get_text(), text) |
690 | self.assertEqual(warning.get_label(), gui.WARNING_MARKUP % text) |
691 | |
692 | def assert_function_decorated(self, decorator, func): |
693 | @@ -294,25 +293,35 @@ |
694 | self.assertTrue(self.ui.get_size_request() <= (736, 525)) |
695 | |
696 | |
697 | -class ControlPanelTestCase(ControlPanelMixinTestCase): |
698 | +class ControlPanelTestCase(BaseTestCase): |
699 | """The test suite for the control panel itself.""" |
700 | |
701 | klass = gui.ControlPanel |
702 | kwargs = {'window_id': 7} |
703 | - ui_filename = 'controlpanel.ui' |
704 | - |
705 | - def test_is_a_vbox(self): |
706 | + |
707 | + def assert_current_tab_correct(self, expected_tab): |
708 | + """Check that the wiget 'expected_tab' is the current page.""" |
709 | + actual = self.ui.get_nth_page(self.ui.get_current_page()) |
710 | + self.assertTrue(expected_tab is actual) |
711 | + |
712 | + def test_is_a_notebook(self): |
713 | """Inherits from gtk.VBox.""" |
714 | - self.assertIsInstance(self.ui, gui.gtk.VBox) |
715 | + self.assertIsInstance(self.ui, gui.gtk.Notebook) |
716 | |
717 | def test_startup_visibility(self): |
718 | """The widget is visible at startup.""" |
719 | - self.assertTrue(self.ui.itself.get_visible(), |
720 | + self.assertTrue(self.ui.get_visible(), |
721 | 'must be visible at startup.') |
722 | |
723 | + def test_startup_props(self): |
724 | + """The tabs and border are not shown.""" |
725 | + self.assertFalse(self.ui.get_show_border(), 'must not show border.') |
726 | + self.assertFalse(self.ui.get_show_tabs(), 'must not show tabs.') |
727 | + |
728 | def test_overview_is_shown_at_startup(self): |
729 | """The overview is shown at startup.""" |
730 | self.assertIsInstance(self.ui.overview, gui.OverviewPanel) |
731 | + self.assert_current_tab_correct(self.ui.overview) |
732 | |
733 | def test_window_id_is_passed_to_child(self): |
734 | """The child gets the window_id.""" |
735 | @@ -321,15 +330,14 @@ |
736 | def test_on_show_management_panel(self): |
737 | """A ManagementPanel is shown when the callback is executed.""" |
738 | self.ui.on_show_management_panel() |
739 | - children = self.ui.get_children() |
740 | - self.assertIsInstance(children[-1], gui.ManagementPanel) |
741 | + self.assert_current_tab_correct(self.ui.management) |
742 | |
743 | def test_on_show_management_panel_is_idempotent(self): |
744 | """Only one ManagementPanel is shown.""" |
745 | self.ui.on_show_management_panel() |
746 | self.ui.on_show_management_panel() |
747 | |
748 | - self.assertEqual(1, len(self.ui.get_children())) |
749 | + self.assert_current_tab_correct(self.ui.management) |
750 | |
751 | def test_credentials_found_shows_account_management_panel(self): |
752 | """On 'credentials-found' signal, the management panel is shown. |
753 | @@ -337,13 +345,13 @@ |
754 | If first signal parameter is False, visible tab should be account. |
755 | |
756 | """ |
757 | - a_token = object() |
758 | - self.ui.overview.emit('credentials-found', False, a_token) |
759 | + self.patch(self.ui.management, 'load', self._set_called) |
760 | + self.ui.overview.emit('credentials-found', False, object()) |
761 | |
762 | - children = self.ui.get_children() |
763 | - self.assertIsInstance(children[-1], gui.ManagementPanel) |
764 | + self.assert_current_tab_correct(self.ui.management) |
765 | self.assertEqual(self.ui.management.notebook.get_current_page(), |
766 | self.ui.management.ACCOUNT_PAGE) |
767 | + self.assertEqual(self._called, ((), {})) |
768 | |
769 | def test_credentials_found_shows_folders_management_panel(self): |
770 | """On 'credentials-found' signal, the management panel is shown. |
771 | @@ -354,8 +362,7 @@ |
772 | a_token = object() |
773 | self.ui.overview.emit('credentials-found', True, a_token) |
774 | |
775 | - children = self.ui.get_children() |
776 | - self.assertIsInstance(children[-1], gui.ManagementPanel) |
777 | + self.assert_current_tab_correct(self.ui.management) |
778 | self.assertEqual(self.ui.management.notebook.get_current_page(), |
779 | self.ui.management.FOLDERS_PAGE) |
780 | |
781 | @@ -364,9 +371,7 @@ |
782 | self.ui.overview.emit('credentials-found', True, object()) |
783 | self.ui.management.emit('local-device-removed') |
784 | |
785 | - children = self.ui.get_children() |
786 | - self.assertEqual(1, len(children)) |
787 | - self.assertTrue(children[0] is self.ui.overview) |
788 | + self.assert_current_tab_correct(self.ui.overview) |
789 | |
790 | |
791 | class UbuntuOneBinTestCase(BaseTestCase): |
792 | @@ -400,6 +405,37 @@ |
793 | ui = self.klass() # no title given |
794 | self.assertEqual(ui.title.label.get_text(), '') |
795 | |
796 | + def test_message_is_a_label_loading(self): |
797 | + """Message is the correct widget.""" |
798 | + self.assertIsInstance(self.ui.message, gui.LabelLoading) |
799 | + self.assertIn(self.ui.message, self.ui.get_children()) |
800 | + |
801 | + def test_on_success(self): |
802 | + """Callback to stop the Loading and clear messages.""" |
803 | + self.ui.on_success() |
804 | + self.assertEqual(self.ui.message.get_label(), '') |
805 | + self.assertFalse(self.ui.message.active) |
806 | + |
807 | + def test_on_success_with_message(self): |
808 | + """Callback to stop the Loading and show a info message.""" |
809 | + msg = 'WOW! <i>this rocks</i>' |
810 | + self.ui.on_success(message=msg) |
811 | + self.assertEqual(self.ui.message.get_label(), msg) |
812 | + self.assertFalse(self.ui.message.active) |
813 | + |
814 | + def test_on_error(self): |
815 | + """Callback to stop the Loading and clear messages.""" |
816 | + self.ui.on_error() |
817 | + self.assert_warning_correct(self.ui.message, gui.VALUE_ERROR) |
818 | + self.assertFalse(self.ui.message.active) |
819 | + |
820 | + def test_on_error_with_message(self): |
821 | + """Callback to stop the Loading and show a info message.""" |
822 | + msg = 'WOW! <i>this does not rock</i> :-/' |
823 | + self.ui.on_error(message=msg) |
824 | + self.assert_warning_correct(self.ui.message, msg) |
825 | + self.assertFalse(self.ui.message.active) |
826 | + |
827 | |
828 | class OverwiewPanelTestCase(ControlPanelMixinTestCase): |
829 | """The test suite for the overview panel.""" |
830 | @@ -494,7 +530,7 @@ |
831 | |
832 | self.ui.on_credentials_found(gui.U1_APP_NAME, TOKEN) |
833 | |
834 | - self.assertFalse(self.ui.get_visible()) |
835 | + self.assertFalse(self.ui.get_property('greyed'), 'Must not be greyed.') |
836 | # assume credentials were in local keyring |
837 | self.assertEqual(self._called, ((self.ui, False, TOKEN), {})) |
838 | |
839 | @@ -507,7 +543,7 @@ |
840 | # now they are! |
841 | self.ui.on_credentials_found(gui.U1_APP_NAME, TOKEN) |
842 | |
843 | - self.assertFalse(self.ui.get_visible()) |
844 | + self.assertFalse(self.ui.get_property('greyed'), 'Must not be greyed.') |
845 | # assume credentials were not in local keyring |
846 | self.assertEqual(self._called, ((self.ui, True, TOKEN), {})) |
847 | |
848 | @@ -708,11 +744,11 @@ |
849 | |
850 | def assert_account_info_correct(self, info): |
851 | """Check that the displayed account info matches 'info'.""" |
852 | - self.assertEqual(self.ui.name_label.get_text(), |
853 | + self.assertEqual(self.ui.name_label.get_label(), |
854 | FAKE_ACCOUNT_INFO['name']) |
855 | - self.assertEqual(self.ui.type_label.get_text(), |
856 | + self.assertEqual(self.ui.type_label.get_label(), |
857 | FAKE_ACCOUNT_INFO['type']) |
858 | - self.assertEqual(self.ui.email_label.get_text(), |
859 | + self.assertEqual(self.ui.email_label.get_label(), |
860 | FAKE_ACCOUNT_INFO['email']) |
861 | |
862 | def test_is_an_ubuntuone_bin(self): |
863 | @@ -727,15 +763,9 @@ |
864 | """Is visible.""" |
865 | self.assertTrue(self.ui.get_visible()) |
866 | |
867 | - def test_type_label_is_loading(self): |
868 | - """Placeholder for type label is a Loading widget.""" |
869 | - self.assertIsInstance(self.ui.type_label, gui.LabelLoading) |
870 | - self.assertIn(self.ui.type_label, self.ui.type_box.get_children()) |
871 | - |
872 | - def test_email_label_is_loading(self): |
873 | - """Placeholder for email label is a Loading widget.""" |
874 | - self.assertIsInstance(self.ui.email_label, gui.LabelLoading) |
875 | - self.assertIn(self.ui.email_label, self.ui.email_box.get_children()) |
876 | + def test_account_info_is_not_visible(self): |
877 | + """Account info is not visible.""" |
878 | + self.assertFalse(self.ui.account.get_visible()) |
879 | |
880 | def test_backend_signals(self): |
881 | """The proper signals are connected to the backend.""" |
882 | @@ -748,18 +778,16 @@ |
883 | """The account info is processed when ready.""" |
884 | self.ui.on_account_info_ready(FAKE_ACCOUNT_INFO) |
885 | self.assert_account_info_correct(FAKE_ACCOUNT_INFO) |
886 | - |
887 | - for widget in (self.ui.name_label, self.ui.type_label, |
888 | - self.ui.email_label): |
889 | - self.assertFalse(widget.active) |
890 | + self.assertTrue(self.ui.account.get_visible()) |
891 | + self.assertFalse(self.ui.message.active) |
892 | + self.assertEqual(self.ui.message.get_label(), '') |
893 | |
894 | def test_on_account_info_error(self): |
895 | """The account info couldn't be retrieved.""" |
896 | self.ui.on_account_info_error() |
897 | - for widget in (self.ui.name_label, self.ui.type_label, |
898 | - self.ui.email_label): |
899 | - self.assert_warning_correct(widget, self.ui.VALUE_ERROR) |
900 | - self.assertFalse(widget.active) |
901 | + self.assertFalse(self.ui.account.get_visible()) |
902 | + self.assertFalse(self.ui.message.active) |
903 | + self.assert_warning_correct(self.ui.message, gui.VALUE_ERROR) |
904 | |
905 | |
906 | class FoldersTestCase(ControlPanelMixinTestCase): |
907 | @@ -799,39 +827,30 @@ |
908 | |
909 | self.assert_backend_called('volumes_info', ()) |
910 | |
911 | - def test_volumes_label(self): |
912 | - """The volumes label is a LabelLoading.""" |
913 | - self.assertIsInstance(self.ui.volumes_label, gui.LabelLoading) |
914 | - self.assertTrue(self.ui.volumes_label.active) |
915 | - self.assertIn(self.ui.volumes_label, self.ui.label_alignment) |
916 | - |
917 | - def test_volumes_label_after_load(self): |
918 | + def test_message_after_load(self): |
919 | """The volumes label is active when contents are load.""" |
920 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) |
921 | self.ui.load() |
922 | |
923 | - self.assertTrue(self.ui.volumes_label.active) |
924 | + self.assertTrue(self.ui.message.active) |
925 | |
926 | - def test_volumes_label_after_non_empty_volumes_info_ready(self): |
927 | + def test_message_after_non_empty_volumes_info_ready(self): |
928 | """The volumes label is a LabelLoading.""" |
929 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) |
930 | |
931 | - self.assertFalse(self.ui.volumes_label.active) |
932 | - self.assertFalse(self.ui.label_alignment.get_visible()) |
933 | + self.assertFalse(self.ui.message.active) |
934 | |
935 | - def test_volumes_label_after_empty_volumes_info_ready(self): |
936 | + def test_message_after_empty_volumes_info_ready(self): |
937 | """When there are no volumes, a notification is shown.""" |
938 | self.ui.on_volumes_info_ready([]) |
939 | |
940 | - self.assertTrue(self.ui.label_alignment.get_visible()) |
941 | - self.assertFalse(self.ui.volumes_label.active) |
942 | - self.assertEqual(self.ui.volumes_label.get_text(), self.ui.NO_VOLUMES) |
943 | + self.assertFalse(self.ui.message.active) |
944 | + self.assertEqual(self.ui.message.get_text(), self.ui.NO_VOLUMES) |
945 | |
946 | def test_on_volumes_info_ready(self): |
947 | """The volumes info is processed when ready.""" |
948 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) |
949 | |
950 | - self.assertFalse(self.ui.label_alignment.get_visible()) |
951 | self.assertEqual(self.ui.folders.get_children(), [self.ui.volumes]) |
952 | |
953 | volumes = self.ui.volumes.get_children() |
954 | @@ -896,10 +915,9 @@ |
955 | def test_on_volumes_info_error(self): |
956 | """The volumes info couldn't be retrieved.""" |
957 | self.ui.on_volumes_info_error() |
958 | - self.assert_warning_correct(warning=self.ui.volumes_label, |
959 | - text=self.ui.VALUE_ERROR) |
960 | - self.assertFalse(self.ui.volumes_label.active) |
961 | - self.assertTrue(self.ui.label_alignment.get_visible()) |
962 | + self.assert_warning_correct(warning=self.ui.message, |
963 | + text=gui.VALUE_ERROR) |
964 | + self.assertFalse(self.ui.message.active) |
965 | |
966 | def test_on_volumes_info_error_after_success(self): |
967 | """The volumes info couldn't be retrieved after a prior success.""" |
968 | @@ -1217,10 +1235,34 @@ |
969 | self.assertEqual(self.ui.backend._signals['DeviceRemoved'], |
970 | [self.ui.on_device_removed]) |
971 | |
972 | - def test_devices_info_is_requested(self): |
973 | + def test_devices_info_is_requested_on_load(self): |
974 | """The devices info is requested to the backend.""" |
975 | + # clean backend calls |
976 | + self.ui.backend._called.pop('devices_info', None) |
977 | + self.ui.load() |
978 | + |
979 | self.assert_backend_called('devices_info', ()) |
980 | |
981 | + def test_message_after_load(self): |
982 | + """The devices label is active when contents are load.""" |
983 | + self.ui.on_devices_info_ready(FAKE_DEVICES_INFO) |
984 | + self.ui.load() |
985 | + |
986 | + self.assertTrue(self.ui.message.active) |
987 | + |
988 | + def test_message_after_non_empty_devices_info_ready(self): |
989 | + """The devices label is a LabelLoading.""" |
990 | + self.ui.on_devices_info_ready(FAKE_DEVICES_INFO) |
991 | + |
992 | + self.assertFalse(self.ui.message.active) |
993 | + |
994 | + def test_message_after_empty_devices_info_ready(self): |
995 | + """When there are no devices, a notification is shown.""" |
996 | + self.ui.on_devices_info_ready([]) |
997 | + |
998 | + self.assertFalse(self.ui.message.active) |
999 | + self.assertEqual(self.ui.message.get_text(), self.ui.NO_DEVICES) |
1000 | + |
1001 | def test_on_devices_info_ready(self): |
1002 | """The devices info is processed when ready.""" |
1003 | self.ui.on_devices_info_ready(FAKE_DEVICES_INFO) |
1004 | @@ -1258,10 +1300,35 @@ |
1005 | for child in self.ui.devices.get_children(): |
1006 | self.assertTrue(self.ui._devices[child.id] is child) |
1007 | |
1008 | + def test_on_devices_info_ready_clears_the_list(self): |
1009 | + """The old devices info is cleared before updated.""" |
1010 | + self.ui.on_devices_info_ready(FAKE_DEVICES_INFO) |
1011 | + self.ui.on_devices_info_ready(FAKE_DEVICES_INFO) |
1012 | + |
1013 | + devices = self.ui.devices.get_children() |
1014 | + self.assertEqual(len(devices), len(FAKE_DEVICES_INFO)) |
1015 | + |
1016 | + def test_on_devices_info_ready_with_no_devices(self): |
1017 | + """When there are no devices, a notification is shown.""" |
1018 | + self.ui.on_devices_info_ready([]) |
1019 | + self.assertEqual(len(self.ui.devices.get_children()), 0) |
1020 | + |
1021 | def test_on_devices_info_error(self): |
1022 | """The devices info couldn't be retrieved.""" |
1023 | self.ui.on_devices_info_error() |
1024 | - # add test! |
1025 | + |
1026 | + self.assert_warning_correct(warning=self.ui.message, |
1027 | + text=gui.VALUE_ERROR) |
1028 | + self.assertFalse(self.ui.message.active) |
1029 | + |
1030 | + def test_on_devices_info_error_after_success(self): |
1031 | + """The devices info couldn't be retrieved after a prior success.""" |
1032 | + self.ui.on_devices_info_ready(FAKE_DEVICES_INFO) |
1033 | + |
1034 | + self.ui.on_devices_info_error() |
1035 | + |
1036 | + self.test_on_devices_info_error() |
1037 | + self.test_on_devices_info_ready_with_no_devices() |
1038 | |
1039 | def test_on_device_removed(self): |
1040 | """When a child device was removed, remove and destroy.""" |
1041 | @@ -1393,8 +1460,9 @@ |
1042 | self.assertEqual(self.ui.backend._signals['AccountInfoError'], |
1043 | [self.ui.on_account_info_error]) |
1044 | |
1045 | - def test_account_info_is_requested(self): |
1046 | + def test_account_info_is_requested_on_load(self): |
1047 | """The account info is requested to the backend.""" |
1048 | + self.ui.load() |
1049 | self.assert_backend_called('account_info', ()) |
1050 | |
1051 | def test_account_panel_is_packed(self): |
1052 | @@ -1429,12 +1497,23 @@ |
1053 | |
1054 | self.assertEqual(self._called, ((), {})) |
1055 | |
1056 | - def test_placeholders_are_loading(self): |
1057 | - """Placeholders for labels are a Loading widget.""" |
1058 | - widgets = (self.ui.quota_label, self.ui.status_label) |
1059 | - for widget in widgets: |
1060 | - self.assertIsInstance(widget, gui.LabelLoading) |
1061 | - self.assertIn(widget, self.ui.status_box.get_children()) |
1062 | + def test_entering_devices_tab_loads_content(self): |
1063 | + """The devices info is loaded when entering the Devices tab.""" |
1064 | + self.patch(self.ui.devices, 'load', self._set_called) |
1065 | + # clean backend calls |
1066 | + self.ui.devices_button.clicked() |
1067 | + |
1068 | + self.assertEqual(self._called, ((), {})) |
1069 | + |
1070 | + def test_quota_placeholder_is_loading(self): |
1071 | + """Placeholders for quota label is a Loading widget.""" |
1072 | + self.assertIsInstance(self.ui.quota_label, gui.LabelLoading) |
1073 | + self.assertIn(self.ui.quota_label, self.ui.quota_box.get_children()) |
1074 | + |
1075 | + def test_file_sync_status_placeholder_is_loading(self): |
1076 | + """Placeholders for file sync label is a Loading widget.""" |
1077 | + self.assertIsInstance(self.ui.status_label, gui.LabelLoading) |
1078 | + self.assertIn(self.ui.status_label, self.ui.status_box.get_children()) |
1079 | |
1080 | def test_on_account_info_ready(self): |
1081 | """The account info is processed when ready.""" |
1082 | @@ -1448,7 +1527,7 @@ |
1083 | """The account info couldn't be retrieved.""" |
1084 | self.ui.on_account_info_error() |
1085 | for widget in (self.ui.quota_label,): |
1086 | - self.assert_warning_correct(widget, self.ui.VALUE_ERROR) |
1087 | + self.assert_warning_correct(widget, gui.VALUE_ERROR) |
1088 | self.assertFalse(widget.active) |
1089 | |
1090 | def test_backend_file_sync_signals(self): |
1091 | @@ -1465,8 +1544,9 @@ |
1092 | for sig, handlers in matches: |
1093 | self.assertEqual(self.ui.backend._signals[sig], handlers) |
1094 | |
1095 | - def test_file_sync_status_is_requested(self): |
1096 | + def test_file_sync_status_is_requested_on_load(self): |
1097 | """The file sync status is requested to the backend.""" |
1098 | + self.ui.load() |
1099 | self.assert_backend_called('file_sync_status', ()) |
1100 | |
1101 | def test_on_file_sync_status_disabled(self): |
1102 | |
1103 | === modified file 'ubuntuone/controlpanel/gtk/tests/test_widgets.py' |
1104 | --- ubuntuone/controlpanel/gtk/tests/test_widgets.py 2010-12-02 16:23:03 +0000 |
1105 | +++ ubuntuone/controlpanel/gtk/tests/test_widgets.py 2010-12-20 21:58:42 +0000 |
1106 | @@ -75,33 +75,26 @@ |
1107 | def test_creation(self): |
1108 | """A LabelLoading can be created.""" |
1109 | self.assertEqual(self.widget.label.get_text(), '') |
1110 | - self.assertFalse(self.widget.label.get_visible()) |
1111 | + self.assertTrue(self.widget.label.get_visible()) |
1112 | |
1113 | self.assertIsInstance(self.widget.loading, widgets.Loading) |
1114 | self.assertTrue(self.widget.loading.get_visible()) |
1115 | - self.assertTrue(self.widget.active) |
1116 | + self.assertTrue(self.widget.active) # loading label is packed |
1117 | |
1118 | def test_stop(self): |
1119 | """Stop hides the Loading widget.""" |
1120 | self.widget.stop() |
1121 | - self.assertTrue(self.widget.label.get_visible()) |
1122 | - self.assertFalse(self.widget.loading.get_visible()) |
1123 | + |
1124 | + self.assertTrue(self.widget.get_child() is self.widget.label) |
1125 | self.assertFalse(self.widget.active) |
1126 | |
1127 | def test_start(self): |
1128 | """Start shows the Loading widget.""" |
1129 | self.widget.start() |
1130 | - self.assertFalse(self.widget.label.get_visible()) |
1131 | - self.assertTrue(self.widget.loading.get_visible()) |
1132 | + |
1133 | + self.assertTrue(self.widget.get_child() is self.widget.loading) |
1134 | self.assertTrue(self.widget.active) |
1135 | |
1136 | - def test_children(self): |
1137 | - """A LabelLoading have proper children.""" |
1138 | - children = self.widget.get_children() |
1139 | - self.assertEqual(2, len(children)) |
1140 | - self.assertTrue(self.widget.label is children[0]) |
1141 | - self.assertTrue(self.widget.loading is children[-1]) |
1142 | - |
1143 | def test_get_text(self): |
1144 | """Text can be queried.""" |
1145 | text = 'Test me.' |
1146 | |
1147 | === modified file 'ubuntuone/controlpanel/gtk/widgets.py' |
1148 | --- ubuntuone/controlpanel/gtk/widgets.py 2010-12-06 17:39:07 +0000 |
1149 | +++ ubuntuone/controlpanel/gtk/widgets.py 2010-12-20 21:58:42 +0000 |
1150 | @@ -49,7 +49,7 @@ |
1151 | self.show_all() |
1152 | |
1153 | |
1154 | -class LabelLoading(gtk.HBox): |
1155 | +class LabelLoading(gtk.Alignment): |
1156 | """A spinner and a label.""" |
1157 | |
1158 | def __init__(self, loading_label, fg_color=None, *args, **kwargs): |
1159 | @@ -57,29 +57,36 @@ |
1160 | self.loading = Loading(loading_label, fg_color=fg_color) |
1161 | |
1162 | self.label = gtk.Label() |
1163 | + self.label.show() |
1164 | if fg_color is not None: |
1165 | self.label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.Color(fg_color)) |
1166 | |
1167 | - self.pack_start(self.label, expand=False) |
1168 | - self.pack_start(self.loading, expand=False) |
1169 | + self.add(self.loading) |
1170 | |
1171 | self.show() |
1172 | + self.set(xalign=0.5, yalign=0.5, xscale=0, yscale=0) |
1173 | + self.set_padding(padding_top=5, padding_bottom=0, |
1174 | + padding_left=5, padding_right=5) |
1175 | self.start() |
1176 | |
1177 | @property |
1178 | def active(self): |
1179 | """Whether the Loading widget is visible or not.""" |
1180 | - return not self.label.get_visible() and self.loading.get_visible() |
1181 | + return self.get_child() is self.loading |
1182 | |
1183 | def start(self): |
1184 | """Show the Loading instead of the Label widget.""" |
1185 | - self.label.hide() |
1186 | - self.loading.show() |
1187 | + for child in self.get_children(): |
1188 | + self.remove(child) |
1189 | + |
1190 | + self.add(self.loading) |
1191 | |
1192 | def stop(self): |
1193 | """Show the label instead of the Loading widget.""" |
1194 | - self.label.show() |
1195 | - self.loading.hide() |
1196 | + for child in self.get_children(): |
1197 | + self.remove(child) |
1198 | + |
1199 | + self.add(self.label) |
1200 | |
1201 | def set_text(self, text): |
1202 | """Set 'text' to be the label's text.""" |
1203 | |
1204 | === modified file 'ubuntuone/controlpanel/tests/test_backend.py' |
1205 | --- ubuntuone/controlpanel/tests/test_backend.py 2010-12-18 17:35:52 +0000 |
1206 | +++ ubuntuone/controlpanel/tests/test_backend.py 2010-12-20 21:58:42 +0000 |
1207 | @@ -55,7 +55,7 @@ |
1208 | "dbpath": "u/abc/def/12345" |
1209 | }, |
1210 | "couchdb_root": "https://couchdb.one.ubuntu.com/u/abc/def/12345", |
1211 | - "email": "andrewpz@protocultura.net", |
1212 | + "email": "andrewpz@protocultura.net",%s |
1213 | "nickname": "Andrew P. Zoilo", |
1214 | "id": 12345, |
1215 | "subscription": { |
1216 | @@ -74,6 +74,13 @@ |
1217 | } |
1218 | """ |
1219 | |
1220 | +CURRENT_PLAN = "Ubuntu One Basic (2 GB) + 1 x 20-Pack with 20 GB (monthly)" |
1221 | +SAMPLE_CURRENT_PLAN = '\n "current_plan": "%s",' % CURRENT_PLAN |
1222 | + |
1223 | +SAMPLE_ACCOUNT_NO_CURRENT_PLAN = SAMPLE_ACCOUNT_JSON % '' |
1224 | +SAMPLE_ACCOUNT_WITH_CURRENT_PLAN = SAMPLE_ACCOUNT_JSON % SAMPLE_CURRENT_PLAN |
1225 | + |
1226 | + |
1227 | SAMPLE_QUOTA_JSON = """ |
1228 | { |
1229 | "total": 53687091200, |
1230 | @@ -89,6 +96,14 @@ |
1231 | "email": "andrewpz@protocultura.net", |
1232 | } |
1233 | |
1234 | +EXPECTED_ACCOUNT_INFO_WITH_CURRENT_PLAN = { |
1235 | + "quota_used": "2350345156", |
1236 | + "quota_total": "53687091200", |
1237 | + "type": CURRENT_PLAN, |
1238 | + "name": "Andrew P. Zoilo", |
1239 | + "email": "andrewpz@protocultura.net", |
1240 | +} |
1241 | + |
1242 | SAMPLE_DEVICES_JSON = """ |
1243 | [ |
1244 | { |
1245 | @@ -322,12 +337,21 @@ |
1246 | def test_account_info(self): |
1247 | """The account_info method exercises its callback.""" |
1248 | # pylint: disable=E1101 |
1249 | - self.be.wc.results[ACCOUNT_API] = SAMPLE_ACCOUNT_JSON |
1250 | + self.be.wc.results[ACCOUNT_API] = SAMPLE_ACCOUNT_NO_CURRENT_PLAN |
1251 | self.be.wc.results[QUOTA_API] = SAMPLE_QUOTA_JSON |
1252 | result = yield self.be.account_info() |
1253 | self.assertEqual(result, EXPECTED_ACCOUNT_INFO) |
1254 | |
1255 | @inlineCallbacks |
1256 | + def test_account_info_with_current_plan(self): |
1257 | + """The account_info method exercises its callback.""" |
1258 | + # pylint: disable=E1101 |
1259 | + self.be.wc.results[ACCOUNT_API] = SAMPLE_ACCOUNT_WITH_CURRENT_PLAN |
1260 | + self.be.wc.results[QUOTA_API] = SAMPLE_QUOTA_JSON |
1261 | + result = yield self.be.account_info() |
1262 | + self.assertEqual(result, EXPECTED_ACCOUNT_INFO_WITH_CURRENT_PLAN) |
1263 | + |
1264 | + @inlineCallbacks |
1265 | def test_account_info_fails(self): |
1266 | """The account_info method exercises its errback.""" |
1267 | # pylint: disable=E1101 |
Works for me.