Merge lp:~nataliabidart/ubuntuone-control-panel/warm-folder-merge into lp:ubuntuone-control-panel

Proposed by Natalia Bidart
Status: Merged
Approved by: Natalia Bidart
Approved revision: 84
Merged at revision: 83
Proposed branch: lp:~nataliabidart/ubuntuone-control-panel/warm-folder-merge
Merge into: lp:ubuntuone-control-panel
Diff against target: 390 lines (+191/-94)
3 files modified
ubuntuone/controlpanel/gtk/gui.py (+27/-9)
ubuntuone/controlpanel/gtk/tests/__init__.py (+75/-1)
ubuntuone/controlpanel/gtk/tests/test_gui.py (+89/-84)
To merge this branch: bzr merge lp:~nataliabidart/ubuntuone-control-panel/warm-folder-merge
Reviewer Review Type Date Requested Status
Eric Casteleijn (community) Approve
Roberto Alsina (community) Approve
Review via email: mp+51617@code.launchpad.net

Commit message

- Added warning message when user is about to subscribe to an UDF that already exists on disk (LP: #674462).

Description of the change

To review, you should try IRL:

* subscribe to a folder which path does exist on disk (merge confirmation requested)
* subscribe to a folder which path does not exist on disk (no confirmation requested)
* unsubscribe from a folder (no confirmation requested)

To post a comment you must log in.
84. By Natalia Bidart

Fixed typo.

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

+1 (code review, no fieldtest)

review: Approve
Revision history for this message
Eric Casteleijn (thisfred) wrote :

Looks good, popup shows when needed. I do see an error (Value could not be retrieved) after I unselect a cloud folder, and the Cloud Folder tab greys out completely. Switching to another tab and back fixes it. Probably not related to this branch.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ubuntuone/controlpanel/gtk/gui.py'
2--- ubuntuone/controlpanel/gtk/gui.py 2011-02-24 12:24:20 +0000
3+++ ubuntuone/controlpanel/gtk/gui.py 2011-02-28 20:05:32 +0000
4@@ -394,6 +394,9 @@
5 FREE_SPACE = _('%(free_space)s available storage')
6 NO_VOLUMES = _('No folders to show.')
7 NAME_NOT_SET = _('[unknown user name]')
8+ CONFIRM_MERGE = _('The contents of your cloud folder will be merged with '
9+ 'your local folder "%(folder_path)s" when subscribing.\n'
10+ 'Do you want to subscribe to this cloud folder?')
11
12 MAX_COLS = 8
13
14@@ -410,6 +413,12 @@
15 self.add(self.itself)
16 self.show_all()
17
18+ kw = dict(parent=main_window,
19+ flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
20+ type=gtk.MESSAGE_WARNING,
21+ buttons=gtk.BUTTONS_YES_NO)
22+ self.confirm_dialog = gtk.MessageDialog(**kw)
23+
24 # name, subscribed, icon name, show toggle, sensitive, icon size,
25 # id, path
26 self._empty_row = ('', False, '', False, False, gtk.ICON_SIZE_MENU,
27@@ -506,15 +515,24 @@
28 """The user toggled 'widget'."""
29 treeiter = self.volumes_store.get_iter(path)
30 volume_id = self.volumes_store.get_value(treeiter, 6)
31- subscribed = not self.volumes_store.get_value(treeiter, 1)
32-
33- self.volumes_store.set_value(treeiter, 1, subscribed)
34-
35- self.backend.change_volume_settings(volume_id,
36- {'subscribed': bool_str(subscribed)},
37- reply_handler=NO_OP, error_handler=error_handler)
38-
39- self.is_processing = True
40+ volume_path = self.volumes_store.get_value(treeiter, 7)
41+ subscribed = self.volumes_store.get_value(treeiter, 1)
42+
43+ response = gtk.RESPONSE_YES
44+ if not subscribed and os.path.exists(volume_path):
45+ self.confirm_dialog.set_markup(self.CONFIRM_MERGE %
46+ {'folder_path': volume_path})
47+ response = self.confirm_dialog.run()
48+ self.confirm_dialog.hide()
49+
50+ if response == gtk.RESPONSE_YES:
51+ subscribed = not subscribed
52+ self.volumes_store.set_value(treeiter, 1, subscribed)
53+ self.backend.change_volume_settings(volume_id,
54+ {'subscribed': bool_str(subscribed)},
55+ reply_handler=NO_OP, error_handler=error_handler)
56+
57+ self.is_processing = True
58
59 def on_volumes_view_row_activated(self, widget, path, *args, **kwargs):
60 """The user double clicked on a row."""
61
62=== modified file 'ubuntuone/controlpanel/gtk/tests/__init__.py'
63--- ubuntuone/controlpanel/gtk/tests/__init__.py 2011-02-16 19:02:02 +0000
64+++ ubuntuone/controlpanel/gtk/tests/__init__.py 2011-02-28 20:05:32 +0000
65@@ -18,11 +18,20 @@
66
67 """The test suite for the GTK UI for the control panel for Ubuntu One."""
68
69+import logging
70+
71 from collections import defaultdict
72
73+from ubuntuone.devtools.handlers import MementoHandler
74+
75 from ubuntuone.controlpanel.gtk import gui
76 from ubuntuone.controlpanel.gtk.tests.test_package_manager import (
77 FakedTransaction)
78+from ubuntuone.controlpanel.tests import TestCase
79+
80+
81+# Attribute 'yyy' defined outside __init__, access to a protected member
82+# pylint: disable=W0201, W0212
83
84
85 FAKE_ACCOUNT_INFO = {'type': 'Payed', 'name': 'Test me',
86@@ -207,7 +216,8 @@
87 self._args = args
88 self._kwargs = kwargs
89 self.was_run = False
90- self.is_visible = True
91+ self.is_visible = False
92+ self.markup = kwargs.get('message_format', None)
93 self.show = lambda: setattr(self, 'is_visible', True)
94 self.hide = lambda: setattr(self, 'is_visible', False)
95 self.response_code = None
96@@ -216,3 +226,67 @@
97 """Set flag and return 'self.response_code'."""
98 self.was_run = True
99 return self.response_code
100+
101+ def set_markup(self, msg):
102+ """Set the markup."""
103+ self.markup = msg
104+
105+
106+class BaseTestCase(TestCase):
107+ """Basics for testing."""
108+
109+ # self.klass is not callable
110+ # pylint: disable=E1102
111+ klass = None
112+ kwargs = {}
113+
114+ def setUp(self):
115+ super(BaseTestCase, self).setUp()
116+ self.patch(gui.os.path, 'expanduser',
117+ lambda path: path.replace('~', USER_HOME))
118+ self.patch(gui.gtk, 'main', lambda: None)
119+ self.patch(gui.gtk, 'MessageDialog', FakedConfirmDialog)
120+ self.patch(gui.dbus, 'SessionBus', FakedSessionBus)
121+ self.patch(gui.dbus, 'Interface', FakedInterface)
122+ self.patch(gui.networkstate, 'NetworkManagerState', FakedNMState)
123+ self.patch(gui.package_manager, 'PackageManager', FakedPackageManager)
124+
125+ if self.klass is not None:
126+ self.ui = self.klass(**self.kwargs)
127+
128+ self.memento = MementoHandler()
129+ self.memento.setLevel(logging.DEBUG)
130+ gui.logger.addHandler(self.memento)
131+
132+ def tearDown(self):
133+ try:
134+ self.ui.hide()
135+ del self.ui
136+ self.ui = None
137+ except AttributeError:
138+ pass
139+ super(BaseTestCase, self).tearDown()
140+
141+ def assert_image_equal(self, image, filename):
142+ """Check that expected and actual represent the same image."""
143+ pb = gui.gtk.gdk.pixbuf_new_from_file(gui.get_data_file(filename))
144+ self.assertEqual(image.get_pixbuf().get_pixels(), pb.get_pixels())
145+
146+ def assert_backend_called(self, method_name, args, backend=None):
147+ """Check that the control panel backend 'method_name' was called."""
148+ if backend is None:
149+ backend = self.ui.backend
150+ self.assertIn(method_name, backend._called)
151+ kwargs = {'reply_handler': gui.NO_OP,
152+ 'error_handler': gui.error_handler}
153+ self.assertEqual(backend._called[method_name], (args, kwargs))
154+
155+ def assert_warning_correct(self, warning, text):
156+ """Check that 'warning' is visible, showing 'text'."""
157+ self.assertTrue(warning.get_visible(), 'Must be visible.')
158+ self.assertEqual(warning.get_label(), gui.WARNING_MARKUP % text)
159+
160+ def assert_function_decorated(self, decorator, func):
161+ """Check that 'func' is decorated with 'decorator'."""
162+ expected = decorator(lambda: None)
163+ self.assertEqual(expected.func_code, func.im_func.func_code)
164
165=== modified file 'ubuntuone/controlpanel/gtk/tests/test_gui.py'
166--- ubuntuone/controlpanel/gtk/tests/test_gui.py 2011-02-23 21:23:30 +0000
167+++ ubuntuone/controlpanel/gtk/tests/test_gui.py 2011-02-28 20:05:32 +0000
168@@ -20,20 +20,15 @@
169
170 from __future__ import division
171
172-import logging
173-
174-from ubuntuone.devtools.handlers import MementoHandler
175-
176 from ubuntuone.controlpanel.gtk import gui
177 from ubuntuone.controlpanel.gtk.tests import (FAKE_ACCOUNT_INFO,
178- FAKE_DEVICE_INFO, FAKE_DEVICES_INFO,
179+ FAKE_DEVICE_INFO, FAKE_DEVICES_INFO, FAKE_FOLDERS_INFO,
180 FAKE_VOLUMES_INFO, FAKE_REPLICATIONS_INFO, ROOT, USER_HOME,
181- FakedNMState, FakedSSOBackend, FakedSessionBus, FakedInterface,
182- FakedPackageManager, FakedConfirmDialog,
183+ BaseTestCase, FakedSSOBackend, FakedConfirmDialog,
184 )
185-from ubuntuone.controlpanel.tests import TOKEN, TestCase
186 from ubuntuone.controlpanel.gtk.tests.test_package_manager import (
187 SUCCESS, FAILURE)
188+from ubuntuone.controlpanel.tests import TOKEN
189
190
191 # Attribute 'yyy' defined outside __init__, access to a protected member
192@@ -42,66 +37,6 @@
193 # pylint: disable=C0302
194
195
196-class BaseTestCase(TestCase):
197- """Basics for testing."""
198-
199- # self.klass is not callable
200- # pylint: disable=E1102
201- klass = None
202- kwargs = {}
203-
204- def setUp(self):
205- super(BaseTestCase, self).setUp()
206- self.patch(gui.os.path, 'expanduser',
207- lambda path: path.replace('~', USER_HOME))
208- self.patch(gui.gtk, 'main', lambda: None)
209- self.patch(gui.gtk, 'MessageDialog', FakedConfirmDialog)
210- self.patch(gui.dbus, 'SessionBus', FakedSessionBus)
211- self.patch(gui.dbus, 'Interface', FakedInterface)
212- self.patch(gui.networkstate, 'NetworkManagerState', FakedNMState)
213- self.patch(gui.package_manager, 'PackageManager', FakedPackageManager)
214-
215- if self.klass is not None:
216- self.ui = self.klass(**self.kwargs)
217-
218- self.memento = MementoHandler()
219- self.memento.setLevel(logging.DEBUG)
220- gui.logger.addHandler(self.memento)
221-
222- def tearDown(self):
223- try:
224- self.ui.hide()
225- del self.ui
226- self.ui = None
227- except AttributeError:
228- pass
229- super(BaseTestCase, self).tearDown()
230-
231- def assert_image_equal(self, image, filename):
232- """Check that expected and actual represent the same image."""
233- pb = gui.gtk.gdk.pixbuf_new_from_file(gui.get_data_file(filename))
234- self.assertEqual(image.get_pixbuf().get_pixels(), pb.get_pixels())
235-
236- def assert_backend_called(self, method_name, args, backend=None):
237- """Check that the control panel backend 'method_name' was called."""
238- if backend is None:
239- backend = self.ui.backend
240- self.assertIn(method_name, backend._called)
241- kwargs = {'reply_handler': gui.NO_OP,
242- 'error_handler': gui.error_handler}
243- self.assertEqual(backend._called[method_name], (args, kwargs))
244-
245- def assert_warning_correct(self, warning, text):
246- """Check that 'warning' is visible, showing 'text'."""
247- self.assertTrue(warning.get_visible(), 'Must be visible.')
248- self.assertEqual(warning.get_label(), gui.WARNING_MARKUP % text)
249-
250- def assert_function_decorated(self, decorator, func):
251- """Check that 'func' is decorated with 'decorator'."""
252- expected = decorator(lambda: None)
253- self.assertEqual(expected.func_code, func.im_func.func_code)
254-
255-
256 class ControlPanelMixinTestCase(BaseTestCase):
257 """The test suite for the control panel widget."""
258
259@@ -896,10 +831,32 @@
260 self.test_on_volumes_info_error()
261 self.test_on_volumes_info_ready_with_no_volumes()
262
263+ def test_clicking_on_row_opens_folder(self):
264+ """The folder activated is opened."""
265+ self.patch(gui, 'uri_hook', self._set_called)
266+ self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
267+
268+ self.ui.volumes_view.row_activated('0:0',
269+ self.ui.volumes_view.get_column(0))
270+
271+ self.assertEqual(self._called,
272+ ((None, gui.FILE_URI_PREFIX + ROOT['path']), {}))
273+
274+
275+class VolumesSubscriptionTestCase(VolumesTestCase):
276+ """The test suite for the volumes panel."""
277+
278+ kwargs = {'main_window': object()}
279+ tree_path = '0:3' # this is the /home/tester/foo folder, not subscribed
280+
281+ def setUp(self):
282+ super(VolumesSubscriptionTestCase, self).setUp()
283+ self.patch(gui.os.path, 'exists', lambda path: True)
284+ self.ui.confirm_dialog.response_code = gui.gtk.RESPONSE_YES
285+ self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
286+
287 def test_on_subscribed_toggled(self):
288 """Clicking on 'subscribed' updates the folder subscription."""
289- self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
290-
291 real_rows = len(FAKE_VOLUMES_INFO)
292 data = zip(range(real_rows)[::2], FAKE_VOLUMES_INFO) # skip emtpy rows
293 for parent, (_, _, volumes) in data:
294@@ -930,8 +887,7 @@
295
296 def test_on_volume_setting_changed(self):
297 """The setting for a volume was successfully changed."""
298- self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
299- self.ui.on_subscribed_toggled(None, "0:0")
300+ self.ui.on_subscribed_toggled(None, self.tree_path)
301
302 self.ui.on_volume_settings_changed(volume_id=None) # id not used
303
304@@ -940,8 +896,7 @@
305
306 def test_on_volume_setting_change_error(self):
307 """The setting for a volume was not successfully changed."""
308- self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
309- self.ui.on_subscribed_toggled(None, "0:0")
310+ self.ui.on_subscribed_toggled(None, self.tree_path)
311
312 self.patch(self.ui, 'load', self._set_called)
313 self.ui.on_volume_settings_change_error(volume_id=None,
314@@ -949,16 +904,66 @@
315 # reload folders list to sanitize the info in volumes_store
316 self.assertTrue(self._called, ((), {}))
317
318- def test_clicking_on_row_opens_folder(self):
319- """The folder activated is opened."""
320- self.patch(gui, 'uri_hook', self._set_called)
321- self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
322-
323- self.ui.volumes_view.row_activated('0:0',
324- self.ui.volumes_view.get_column(0))
325-
326- self.assertEqual(self._called,
327- ((None, gui.FILE_URI_PREFIX + ROOT['path']), {}))
328+ def test_confirm_dialog(self):
329+ """The confirmation dialog is correct."""
330+ dialog = self.ui.confirm_dialog
331+
332+ self.assertEqual(dialog._args, ())
333+ flags = gui.gtk.DIALOG_MODAL | gui.gtk.DIALOG_DESTROY_WITH_PARENT
334+ kwargs = dict(parent=self.kwargs['main_window'],
335+ flags=flags, type=gui.gtk.MESSAGE_WARNING,
336+ buttons=gui.gtk.BUTTONS_YES_NO)
337+ self.assertEqual(dialog._kwargs, kwargs)
338+
339+ def test_subscribe_shows_confirmation_dialog(self):
340+ """Clicking on subscribe displays a confirmation dialog."""
341+ self.ui.on_subscribed_toggled(None, self.tree_path)
342+
343+ path = FAKE_FOLDERS_INFO[0]['path']
344+ self.assertTrue(self.ui.confirm_dialog.was_run, 'dialog was run')
345+ self.assertEqual(self.ui.confirm_dialog.markup,
346+ self.ui.CONFIRM_MERGE % {'folder_path': path})
347+ self.assertFalse(self.ui.confirm_dialog.is_visible, 'dialog was hid')
348+
349+ def test_subscribe_does_not_call_backend_if_dialog_closed(self):
350+ """Backend is not called if users closes the confirmation dialog."""
351+ self.ui.confirm_dialog.response_code = gui.gtk.RESPONSE_DELETE_EVENT
352+ self.ui.on_subscribed_toggled(None, self.tree_path)
353+
354+ self.assertNotIn('change_volume_settings', self.ui.backend._called)
355+ self.assertFalse(self.ui.is_processing)
356+
357+ def test_subscribe_does_not_call_backend_if_answer_is_no(self):
358+ """Backend is not called if users clicks on 'No'."""
359+ self.ui.confirm_dialog.response_code = gui.gtk.RESPONSE_NO
360+ self.ui.on_subscribed_toggled(None, self.tree_path)
361+
362+ self.assertNotIn('change_volume_settings', self.ui.backend._called)
363+ self.assertFalse(self.ui.is_processing)
364+
365+ def test_no_confirmation_if_no_local_folder(self):
366+ """The confirmation dialog is not shown if local folder not present."""
367+ self.patch(gui.os.path, 'exists', lambda path: False)
368+ self.ui.on_subscribed_toggled(None, self.tree_path)
369+
370+ self.assertFalse(self.ui.confirm_dialog.was_run, 'dialog was not run')
371+ self.assertFalse(self.ui.confirm_dialog.is_visible, 'dialog was hid')
372+
373+ def test_no_confirmation_if_unsubscribing(self):
374+ """The confirmation dialog is not shown if unsubscribing."""
375+ self.ui.on_subscribed_toggled(None, self.tree_path)
376+
377+ treeiter = self.ui.volumes_store.get_iter(self.tree_path)
378+ assert self.ui.volumes_store.get_value(treeiter, 1)
379+
380+ # reset flags
381+ self.ui.confirm_dialog.was_run = False
382+ self.ui.confirm_dialog.is_visible = False
383+
384+ self.ui.on_subscribed_toggled(None, self.tree_path)
385+
386+ self.assertFalse(self.ui.confirm_dialog.was_run, 'dialog was not run')
387+ self.assertFalse(self.ui.confirm_dialog.is_visible, 'dialog was hid')
388
389
390 class DeviceTestCase(ControlPanelMixinTestCase):

Subscribers

People subscribed via source and target branches