Merge lp:~ralsina/ubuntuone-windows-installer/fix_800376 into lp:ubuntuone-windows-installer

Proposed by Roberto Alsina
Status: Merged
Approved by: Roberto Alsina
Approved revision: 11
Merged at revision: 10
Proposed branch: lp:~ralsina/ubuntuone-windows-installer/fix_800376
Merge into: lp:ubuntuone-windows-installer
Diff against target: 649 lines (+450/-22)
4 files modified
data/qt/local_folders.ui (+167/-0)
ubuntuone_installer/gui/qt/gui.py (+11/-16)
ubuntuone_installer/gui/qt/local_folders.py (+159/-0)
ubuntuone_installer/gui/qt/tests/test_gui.py (+113/-6)
To merge this branch: bzr merge lp:~ralsina/ubuntuone-windows-installer/fix_800376
Reviewer Review Type Date Requested Status
Manuel de la Peña (community) Approve
Natalia Bidart (community) Approve
Review via email: mp+66194@code.launchpad.net

Commit message

First branch of the implementation of "Syncing your computer to the cloud" pages.

Description of the change

Initial implementation of a page to select which local folders will be synced to Ubuntu One.

Currently it doesn't do UDF creation or validation, because that functionality should be imported from control panel.

To test IRL (windows only):

You need ubuntuone-client and ubuntu-one-control-panel in your PYTHONPATH, for example:

PYTHONPATH=.;..\ubuntuone-client;..\ubuntuone-control-panel

On another terminal, start the SSO windows client.

Then you can start the wizard:

python bin\ubuntuone-installer-qt

You should be able to click "Agree & Install", then login with a valid user/password, then click "Next" and see this page.

In the page, you should get your "My Documents" folder added by default, and after some seconds,
the space used should be visible on that item and in the header of the list.

I intentionally faked the quota to be very low. Because of that, you should see a widget asking you to buy more space.

If you click "remove" in that row, the item should disappear, and the space used adjust accordingly to 0.

This branch doesn't close the bug, it's just a step towards that.

To post a comment you must log in.
Revision history for this message
Natalia Bidart (nataliabidart) wrote :

A couple of needs fixing:

* always call super() instead of explicitly calling the parent class

* the diff seems to add several (at first) not needed blank lines, can you please remove those, if they are not needed? (we usually do not add empty lines after the docstrings)

* by convention, setUp and tearDown, if defined, should be the first 2 methods after the class declaration

* this docstring:

506 + """After removing all folders, offer_frame should be hidden.
507 +
508 + Push the user over quota, it should be visible"""

should be:

506 + """After removing all folders, offer_frame should be hidden.
507 +
508 + Push the user over quota, it should be visible.

          """

review: Needs Fixing
Revision history for this message
Roberto Alsina (ralsina) wrote :

> A couple of needs fixing:
>
> * always call super() instead of explicitly calling the parent class

Ok.

>
> * the diff seems to add several (at first) not needed blank lines, can you
> please remove those, if they are not needed? (we usually do not add empty
> lines after the docstrings)

Ok.

> * by convention, setUp and tearDown, if defined, should be the first 2 methods
> after the class declaration

Ok.

> * this docstring:
>
> 506 + """After removing all folders, offer_frame should be hidden.
> 507 +
> 508 + Push the user over quota, it should be visible"""
>
> should be:
>
> 506 + """After removing all folders, offer_frame should be hidden.
> 507 +
> 508 + Push the user over quota, it should be visible.
>
> """

Ok.

All of those hsould be fixed now.

9. By Roberto Alsina

Implement the page that lets the user choose between "sync now" / "sync later" / "sync selectively"

10. By Roberto Alsina

Resolve conflicts with trunk

Revision history for this message
Natalia Bidart (nataliabidart) :
review: Approve
11. By Roberto Alsina

PEP 252

Revision history for this message
Manuel de la Peña (mandel) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'data/qt/local_folders.ui'
2--- data/qt/local_folders.ui 1970-01-01 00:00:00 +0000
3+++ data/qt/local_folders.ui 2011-06-29 15:13:36 +0000
4@@ -0,0 +1,167 @@
5+<?xml version="1.0" encoding="UTF-8"?>
6+<ui version="4.0">
7+ <class>Form</class>
8+ <widget class="QWidget" name="Form">
9+ <property name="geometry">
10+ <rect>
11+ <x>0</x>
12+ <y>0</y>
13+ <width>460</width>
14+ <height>536</height>
15+ </rect>
16+ </property>
17+ <property name="windowTitle">
18+ <string>Form</string>
19+ </property>
20+ <layout class="QVBoxLayout" name="verticalLayout_2">
21+ <item>
22+ <widget class="QLabel" name="label">
23+ <property name="text">
24+ <string>Ok! Now it's time to choose wich folder sync to the cloud from this computer.
25+We started by suggesting a few</string>
26+ </property>
27+ <property name="textFormat">
28+ <enum>Qt::RichText</enum>
29+ </property>
30+ <property name="wordWrap">
31+ <bool>true</bool>
32+ </property>
33+ </widget>
34+ </item>
35+ <item>
36+ <layout class="QHBoxLayout" name="horizontalLayout_2">
37+ <item>
38+ <widget class="QLabel" name="label_2">
39+ <property name="text">
40+ <string>Folders on my computer</string>
41+ </property>
42+ </widget>
43+ </item>
44+ <item>
45+ <spacer name="horizontalSpacer_3">
46+ <property name="orientation">
47+ <enum>Qt::Horizontal</enum>
48+ </property>
49+ <property name="sizeHint" stdset="0">
50+ <size>
51+ <width>40</width>
52+ <height>20</height>
53+ </size>
54+ </property>
55+ </spacer>
56+ </item>
57+ <item>
58+ <widget class="QPushButton" name="pushButton_2">
59+ <property name="text">
60+ <string>Add a folder</string>
61+ </property>
62+ </widget>
63+ </item>
64+ </layout>
65+ </item>
66+ <item>
67+ <widget class="QTreeWidget" name="folder_list">
68+ <attribute name="headerStretchLastSection">
69+ <bool>false</bool>
70+ </attribute>
71+ <column>
72+ <property name="text">
73+ <string>My Folders</string>
74+ </property>
75+ </column>
76+ <column>
77+ <property name="text">
78+ <string>Space (Total)</string>
79+ </property>
80+ </column>
81+ <column>
82+ <property name="text">
83+ <string>Remove</string>
84+ </property>
85+ </column>
86+ </widget>
87+ </item>
88+ <item>
89+ <widget class="QCheckBox" name="checkBox">
90+ <property name="text">
91+ <string>Connect automatically when computer starts</string>
92+ </property>
93+ </widget>
94+ </item>
95+ <item>
96+ <widget class="QCheckBox" name="checkBox_2">
97+ <property name="text">
98+ <string>Automatically sync all selected folders from this computer to the cloud</string>
99+ </property>
100+ </widget>
101+ </item>
102+ <item>
103+ <widget class="QFrame" name="offer_frame">
104+ <property name="frameShape">
105+ <enum>QFrame::StyledPanel</enum>
106+ </property>
107+ <property name="frameShadow">
108+ <enum>QFrame::Raised</enum>
109+ </property>
110+ <layout class="QVBoxLayout" name="verticalLayout">
111+ <property name="leftMargin">
112+ <number>0</number>
113+ </property>
114+ <property name="rightMargin">
115+ <number>0</number>
116+ </property>
117+ <item>
118+ <widget class="QLabel" name="offer_label">
119+ <property name="text">
120+ <string>The folders you have selected to sync take over your 2GB space. You can remove some folders or add some extra space</string>
121+ </property>
122+ <property name="wordWrap">
123+ <bool>true</bool>
124+ </property>
125+ </widget>
126+ </item>
127+ <item>
128+ <layout class="QHBoxLayout" name="horizontalLayout">
129+ <item>
130+ <spacer name="horizontalSpacer">
131+ <property name="orientation">
132+ <enum>Qt::Horizontal</enum>
133+ </property>
134+ <property name="sizeHint" stdset="0">
135+ <size>
136+ <width>40</width>
137+ <height>20</height>
138+ </size>
139+ </property>
140+ </spacer>
141+ </item>
142+ <item>
143+ <widget class="QPushButton" name="pushButton">
144+ <property name="text">
145+ <string>add more space</string>
146+ </property>
147+ </widget>
148+ </item>
149+ <item>
150+ <spacer name="horizontalSpacer_2">
151+ <property name="orientation">
152+ <enum>Qt::Horizontal</enum>
153+ </property>
154+ <property name="sizeHint" stdset="0">
155+ <size>
156+ <width>40</width>
157+ <height>20</height>
158+ </size>
159+ </property>
160+ </spacer>
161+ </item>
162+ </layout>
163+ </item>
164+ </layout>
165+ </widget>
166+ </item>
167+ </layout>
168+ </widget>
169+ <resources/>
170+ <connections/>
171+</ui>
172
173=== modified file 'ubuntuone_installer/gui/qt/gui.py'
174--- ubuntuone_installer/gui/qt/gui.py 2011-06-28 19:45:45 +0000
175+++ ubuntuone_installer/gui/qt/gui.py 2011-06-29 15:13:36 +0000
176@@ -72,6 +72,7 @@
177 license_ui,
178 congratulations_ui,
179 )
180+from ubuntuone_installer.gui.qt.local_folders import LocalFoldersPage
181 from ubuntuone_installer.gui.qt.sync_now_or_later import SyncNowOrLaterPage
182
183 _ = gettext.gettext
184@@ -87,7 +88,7 @@
185 """Wizard Page that displays the license info and links to the GPL."""
186
187 def __init__(self, parent=None):
188- QtGui.QWizardPage.__init__(self, parent)
189+ super(LicensePage, self).__init__(parent)
190 self.ui = license_ui.Ui_Form()
191 self.ui.setupUi(self)
192 self.setCommitPage(False)
193@@ -97,7 +98,6 @@
194
195 def initializePage(self):
196 """Setup UI details."""
197-
198 # Set the right texts and connections for buttons
199 self.setButtonText(QtGui.QWizard.NextButton, _("Agree && Install"))
200 self.setButtonText(QtGui.QWizard.CancelButton,
201@@ -124,7 +124,6 @@
202
203 def print_document(self, button_id):
204 """Print the document displayed in textBrowser."""
205-
206 if button_id == QtGui.QWizard.CustomButton1:
207 document = self.ui.textBrowser.document()
208 printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution)
209@@ -143,7 +142,7 @@
210 """Wizard Page that lets the user Sign into Ubuntu One."""
211
212 def __init__(self, ui=None, controller=None, parent=None):
213- QtGui.QWizardPage.__init__(self, parent)
214+ super(SignInPage, self).__init__(parent)
215 self.ui = ui
216 self.ui.setupUi(self)
217 self.controller = controller
218@@ -155,7 +154,6 @@
219
220 def initializePage(self):
221 """Setup UI details."""
222-
223 self.setButtonText(QtGui.QWizard.CancelButton,
224 _("Close window and setup later"))
225 # Layout without custom button 1,
226@@ -175,7 +173,7 @@
227 """Wizard Page that lets a current user Sign into Ubuntu One."""
228
229 def __init__(self, ui=None, controller=None, parent=None):
230- QtGui.QWizardPage.__init__(self, parent)
231+ super(CurrentUserSignInPage, self).__init__(parent)
232 self.ui = ui
233 self.ui.setupUi(self)
234 self.controller = controller
235@@ -185,9 +183,6 @@
236 # Invalid names of Qt-inherited methods
237 # pylint: disable=C0103
238
239- def initializePage(self):
240- """Setup UI details."""
241-
242 def nextId(self):
243 """Provide the next id."""
244 return self.next
245@@ -197,7 +192,7 @@
246 """Shown after SSO login, before setup."""
247
248 def __init__(self, ui=None, controller=None, parent=None):
249- QtGui.QWizardPage.__init__(self, parent)
250+ super(SuccessPage, self).__init__(parent)
251 self.ui = ui
252 self.ui.setupUi(self)
253 self.controller = controller
254@@ -225,7 +220,7 @@
255 """Final page of the wizard."""
256
257 def __init__(self, parent=None):
258- QtGui.QWizardPage.__init__(self, parent)
259+ super(CongratulationsPage, self).__init__(parent)
260 self.ui = congratulations_ui.Ui_Form()
261 self.ui.setupUi(self)
262 self.setFinalPage(True)
263@@ -235,7 +230,6 @@
264
265 def initializePage(self):
266 """Setup UI details."""
267-
268 # We need custom buttons
269 self.wizard().setButtonText(QtGui.QWizard.FinishButton,
270 _("Go to my Ubuntu One dashboard"))
271@@ -269,7 +263,6 @@
272
273 def __init__(self, close_callback=None):
274 """Initialize this instance."""
275-
276 # Used to decide the next page dynamically
277 self._next_id = None
278
279@@ -279,7 +272,7 @@
280 self.tc_url = TC_URL
281 self.ping_url = PING_URL
282
283- QtGui.QWizard.__init__(self)
284+ super(MainWindow, self).__init__()
285 self.close_callback = close_callback
286
287 self.creds = Credentials(
288@@ -346,6 +339,8 @@
289
290 # End of SSO pages
291
292+ self.local_folders_page = LocalFoldersPage()
293+ self.local_folders_page_id = self.addPage(self.local_folders_page)
294 self.SYNC_NOW_OR_LATER_PAGE = self.addPage(SyncNowOrLaterPage())
295 self.CONGRATULATIONS_PAGE = self.addPage(CongratulationsPage())
296
297@@ -363,13 +358,13 @@
298 """Called on successful login."""
299 self._next_id = self.SUCCESS_PAGE
300 self.next()
301- self._next_id = self.SYNC_NOW_OR_LATER_PAGE
302+ self._next_id = self.local_folders_page_id
303
304 def registration_success_slot(self):
305 """Called on successful registration."""
306 self._next_id = self.SUCCESS_PAGE
307 self.next()
308- self._next_id = self.SYNC_NOW_OR_LATER_PAGE
309+ self._next_id = self.local_folders_page_id
310
311 def done(self, result):
312 """The main window is being closed, call any custom callback."""
313
314=== added file 'ubuntuone_installer/gui/qt/local_folders.py'
315--- ubuntuone_installer/gui/qt/local_folders.py 1970-01-01 00:00:00 +0000
316+++ ubuntuone_installer/gui/qt/local_folders.py 2011-06-29 15:13:36 +0000
317@@ -0,0 +1,159 @@
318+# -*- coding: utf-8 -*-
319+
320+# Authors: Roberto Alsina <roberto.alsina@canonical.com>
321+#
322+# Copyright 2011 Canonical Ltd.
323+#
324+# This program is free software: you can redistribute it and/or modify it
325+# under the terms of the GNU General Public License version 3, as published
326+# by the Free Software Foundation.
327+#
328+# This program is distributed in the hope that it will be useful, but
329+# WITHOUT ANY WARRANTY; without even the implied warranties of
330+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
331+# PURPOSE. See the GNU General Public License for more details.
332+#
333+# You should have received a copy of the GNU General Public License along
334+# with this program. If not, see <http://www.gnu.org/licenses/>.
335+
336+"""Widget to create UDFs in the Windows Install Wizard."""
337+
338+import ctypes
339+import gettext
340+import os
341+import threading
342+import Queue
343+
344+from PyQt4 import QtCore, QtGui
345+
346+from ubuntuone.controlpanel.gui import humanize
347+
348+from ubuntuone_installer.gui.qt.ui import local_folders_ui
349+
350+_ = gettext.gettext
351+
352+
353+class FolderItem(QtGui.QTreeWidgetItem):
354+ """Class representing a folder in the folder list UI."""
355+ def __init__(self, strings, path=None, queue=None):
356+ super(FolderItem, self).__init__(strings)
357+ self.thread = CalculateSize(path, queue)
358+ self.thread.start()
359+ self.size = None
360+ self.path = path
361+
362+
363+class CalculateSize(threading.Thread):
364+ """Class to calculate the size of a folder in the background."""
365+ def __init__(self, path_name, queue):
366+ self.path_name = path_name
367+ self.queue = queue
368+ self._stop = False
369+ super(CalculateSize, self).__init__()
370+ self.daemon = True
371+
372+ def run(self):
373+ total_size = 0
374+ for dirpath, dirnames, filenames in os.walk(self.path_name):
375+ for f in filenames:
376+ fp = os.path.join(dirpath, f)
377+ total_size += os.path.getsize(fp)
378+ self.queue.put([self.path_name, total_size])
379+
380+
381+class LocalFoldersPage(QtGui.QWizardPage):
382+ """Wizard page to create UDFs in the Windows Installer."""
383+ def __init__(self, parent=None):
384+ super(LocalFoldersPage, self).__init__(parent)
385+ self.setTitle(_("Syncing your computer with the cloud"))
386+ self.ui = local_folders_ui.Ui_Form()
387+ self.ui.setupUi(self)
388+
389+ header_view = self.ui.folder_list.header()
390+ header_view.setResizeMode(0, header_view.Stretch)
391+
392+ self.queue = Queue.Queue()
393+ self.timer = QtCore.QTimer()
394+ self.items = {}
395+ for folder_name in self.default_folders():
396+ self.add_folder(folder_name)
397+ self.update_sizes()
398+ self.timer.start(2000)
399+ self.timer.timeout.connect(self.update_sizes)
400+
401+ def initializePage(self):
402+ self.wizard()._next_id = None
403+
404+ def quota(self):
405+ """The quota available to the user."""
406+ # FIXME: get this from real life
407+ return 2 * 1024 * 1024
408+
409+ def default_folders(self):
410+ """Return a list of the folders to add by default."""
411+ # Special Folder "My Documents"
412+ dll = ctypes.windll.shell32
413+ buf = ctypes.create_string_buffer(300)
414+ dll.SHGetSpecialFolderPathA(None, buf, 0x0005, False)
415+ return [buf.value, ]
416+
417+ def add_folder(self, path):
418+ """Add a folder to the list."""
419+ if path in self.items:
420+ return None
421+ # FIXME: the path should actually be sent to u1cp to verify as valid
422+ item = FolderItem([path, "", _("Remove")], path=path, queue=self.queue)
423+ self.ui.folder_list.addTopLevelItem(item)
424+ self.items[path] = item
425+ return item
426+
427+ def update_sizes(self):
428+ """Poll the queue were the threads put the size info."""
429+ try:
430+ path, size = self.queue.get(False)
431+ item = self.items.get(path)
432+ if item:
433+ item.size = size
434+ item.setText(1, humanize(size))
435+ except Queue.Empty:
436+ pass
437+ total = 0
438+ for path, item in self.items.items():
439+ if item.size is None:
440+ total = _("Calculating")
441+ break
442+ total += item.size
443+
444+ if isinstance(total, long):
445+ self.show_hide_offer(total)
446+ total = humanize(total)
447+ else:
448+ self.show_hide_offer(0)
449+ self.ui.folder_list.headerItem().setText(1, _("Space (%s)" % total))
450+
451+ def show_hide_offer(self, cur_size):
452+ """Show or hide the offer to buy space according to the total size."""
453+ quota = self.quota()
454+
455+ if cur_size > quota:
456+ self.ui.offer_frame.setVisible(True)
457+ else:
458+ self.ui.offer_frame.setVisible(False)
459+
460+ self.ui.offer_label.setText(_("The folders you have selected to sync "
461+ "take over your %s space. You can remove some folders or add "
462+ "some extra space" % humanize(quota)))
463+
464+ def stop_threads(self):
465+ """Stop all pending threads."""
466+ for path, item in self.items:
467+ item.thread._stop = True
468+
469+ def on_folder_list_itemClicked(self, item, column):
470+ """Delete folder from the list."""
471+ if column == 2:
472+ del(self.items[item.path])
473+ item.thread._stop = True
474+ self.ui.folder_list.takeTopLevelItem(
475+ self.ui.folder_list.indexOfTopLevelItem(item))
476+ self.update_sizes()
477
478=== modified file 'ubuntuone_installer/gui/qt/tests/test_gui.py'
479--- ubuntuone_installer/gui/qt/tests/test_gui.py 2011-06-28 19:18:35 +0000
480+++ ubuntuone_installer/gui/qt/tests/test_gui.py 2011-06-29 15:13:36 +0000
481@@ -19,6 +19,11 @@
482
483 """Tests for the Qt UI."""
484
485+import os
486+import Queue
487+import shutil
488+import tempfile
489+
490 from PyQt4 import QtCore
491
492 from ubuntuone.credentials import (
493@@ -32,6 +37,7 @@
494 from ubuntuone_installer.gui.qt import gui
495 from ubuntuone_installer.gui.qt.tests import BaseTestCase
496 from ubuntuone_installer.gui.qt.embedded_sso import UbuntuSSOClientGUI
497+from ubuntuone_installer.gui.qt import local_folders
498
499 TOKEN = {u'consumer_key': u'xQ7xDAz',
500 u'consumer_secret': u'KzCJWCTNbbntwfyCKKjomJDzlgqxLy',
501@@ -96,7 +102,8 @@
502
503 def test_start_control_panel_on_finishing(self):
504 """If done is called with result=1, the control panel
505- should be called."""
506+ should be called.
507+ """
508 self.patch(gui.subprocess, "Popen", self._set_called)
509 self.ui.done(result=1)
510 self.assertEqual(self._called,
511@@ -104,7 +111,8 @@
512
513 def test_not_start_control_panel_on_cancel(self):
514 """If done is called with result=0, the control panel
515- should NOT be called."""
516+ should NOT be called.
517+ """
518 self.patch(gui.subprocess, "Popen", self._set_called)
519 self.ui.done(result=0)
520 self.assertEqual(self._called, False)
521@@ -143,8 +151,7 @@
522 congrats_page.ui.progressContainer.isVisible(), False)
523
524 def test_credential_parameters(self):
525- """Test if the credentials are created the same way
526- as in control panel."""
527+ """Compare credential parameters with control panel's."""
528 self.assertEqual(self.ui.creds.kwargs, {
529 'app_name': APP_NAME,
530 'ui_module': "ubuntuone_installer.gui.qt.embedded_sso",
531@@ -160,7 +167,6 @@
532
533 def setUp(self):
534 """Initialize this test instance."""
535-
536 self.kwargs = {
537 'app_name': APP_NAME,
538 'tc_url': TC_URL,
539@@ -171,8 +177,109 @@
540
541 def test_sso_client_gui(self):
542 """Ensure the class stores the right parameters."""
543-
544 self.assertEqual(isinstance(
545 self.ui.controller,
546 ubuntu_sso.qt.controllers.UbuntuSSOWizardController), True)
547 self.assertEqual(self.ui.view, True)
548+
549+
550+class LocalFoldersTestCase(BaseTestCase):
551+ """Test the LocalFoldersPage code."""
552+
553+ class_ui = local_folders.LocalFoldersPage
554+
555+ def setUp(self):
556+ """Initialize this test instance."""
557+ # Create a test folder with a known size
558+ self.tmpdir = tempfile.mkdtemp()
559+ f = open(os.path.join(self.tmpdir, 'test_file'), 'wb')
560+ f.write(" " * 600)
561+ f.close()
562+ os.mkdir(os.path.join(self.tmpdir, 'test_dir'))
563+ f = open(os.path.join(self.tmpdir, 'test_dir', 'test_file_2'), 'wb')
564+ f.write(" " * 737)
565+ f.close()
566+ super(LocalFoldersTestCase, self).setUp()
567+
568+ def tearDown(self):
569+ """Remove the temporary tree."""
570+ shutil.rmtree(self.tmpdir)
571+ BaseTestCase.tearDown(self)
572+
573+ def test_size_calculation(self):
574+ """Test the recursive folder size calculation."""
575+ self.queue = Queue.Queue()
576+ self.csize = local_folders.CalculateSize(self.tmpdir, self.queue)
577+ self.csize.run()
578+ path, size = self.queue.get()
579+ self.assertEqual(path, self.tmpdir)
580+ self.assertEqual(size, 1337)
581+
582+ def test_item_addition_removal(self):
583+ """Add an item (plus the default one), then remove them."""
584+ self.ui.add_folder(self.tmpdir)
585+ self.assertEqual(2, self.ui.ui.folder_list.topLevelItemCount())
586+ self.ui.on_folder_list_itemClicked(
587+ self.ui.ui.folder_list.topLevelItem(0), 2)
588+ self.ui.on_folder_list_itemClicked(
589+ self.ui.ui.folder_list.topLevelItem(0), 2)
590+ self.assertEqual(0, self.ui.ui.folder_list.topLevelItemCount())
591+ self.assertEqual({}, self.ui.items)
592+
593+ def test_total_size(self):
594+ """Test that the header reflects the change in item sizes."""
595+ while self.ui.ui.folder_list.topLevelItemCount():
596+ self.ui.on_folder_list_itemClicked(
597+ self.ui.ui.folder_list.topLevelItem(0), 2)
598+ self.assertEqual(0, self.ui.ui.folder_list.topLevelItemCount())
599+ item = self.ui.add_folder(self.tmpdir)
600+ item.size = 1337
601+ self.ui.update_sizes()
602+ self.assertEqual(unicode(self.ui.ui.folder_list.headerItem().text(1)),
603+ u"Space (1337)")
604+
605+ def test_add_twice(self):
606+ """Behaviour for adding the same folder twice:
607+
608+ * It's added only once.
609+ """
610+ while self.ui.ui.folder_list.topLevelItemCount():
611+ self.ui.on_folder_list_itemClicked(
612+ self.ui.ui.folder_list.topLevelItem(0), 2)
613+ self.assertEqual(0, self.ui.ui.folder_list.topLevelItemCount())
614+ self.ui.add_folder(self.tmpdir)
615+ self.ui.add_folder(self.tmpdir)
616+ self.assertEqual(1, self.ui.ui.folder_list.topLevelItemCount())
617+
618+ def test_add_missing_folder(self):
619+ """Behaviour for adding a folder that doesn't exist:
620+
621+ * It's added.
622+ * Has size 0.
623+ """
624+
625+ while self.ui.ui.folder_list.topLevelItemCount():
626+ self.ui.on_folder_list_itemClicked(
627+ self.ui.ui.folder_list.topLevelItem(0), 2)
628+ self.assertEqual(0, self.ui.ui.folder_list.topLevelItemCount())
629+ item = self.ui.add_folder(os.path.join("xyzzy", "xyzzy", "xyzzy"))
630+ self.assertEqual(1, self.ui.ui.folder_list.topLevelItemCount())
631+ item.thread.run()
632+ self.ui.update_sizes()
633+ self.assertEqual(0, item.size)
634+ # world did not explode
635+
636+ def test_over_quota(self):
637+ """After removing all folders, offer_frame should be hidden.
638+
639+ Push the user over quota, it should be visible.
640+ """
641+ self.patch(self.ui.ui.offer_frame, "setVisible", self._set_called)
642+ while self.ui.ui.folder_list.topLevelItemCount():
643+ self.ui.on_folder_list_itemClicked(
644+ self.ui.ui.folder_list.topLevelItem(0), 2)
645+ self.assertEqual(0, self.ui.ui.folder_list.topLevelItemCount())
646+ self.ui.update_sizes()
647+ self.assertEqual(self._called, ((False,), {}))
648+ self.ui.show_hide_offer(self.ui.quota() + 1)
649+ self.assertEqual(self._called, ((True,), {}))

Subscribers

People subscribed via source and target branches