Merge lp:~nataliabidart/magicicada-gui/get-metadata-ui into lp:magicicada-gui

Proposed by Natalia Bidart
Status: Merged
Approved by: Facundo Batista
Approved revision: 49
Merged at revision: 48
Proposed branch: lp:~nataliabidart/magicicada-gui/get-metadata-ui
Merge into: lp:magicicada-gui
Diff against target: 771 lines (+436/-50)
3 files modified
data/ui/gui.glade (+127/-0)
magicicada/__init__.py (+39/-2)
magicicada/tests/test_magicicada.py (+270/-48)
To merge this branch: bzr merge lp:~nataliabidart/magicicada-gui/get-metadata-ui
Reviewer Review Type Date Requested Status
Facundo Batista Approve
Review via email: mp+27462@code.launchpad.net

Description of the change

Get raw metadata functionality for the GUI.

Depends on lp:~facundo/magicicada/show-md for running the binary in real life, not for tests (since those use a faked syncdaemon).
This branch may be tweaked if we decide that the "code" for the SD will be the same as the path to which metadata is requested.

To post a comment you must log in.
Revision history for this message
Facundo Batista (facundo) wrote :

User should be able to open two "metadata" popups (this is specially useful to compare information).

Right now, if you click again on metada to see other file, the previous content is lost.

review: Needs Fixing
Revision history for this message
Natalia Bidart (nataliabidart) wrote :

I opened a bug for the requested behavior: https://bugs.launchpad.net/magicicada/+bug/594268

50. By Natalia Bidart

API no longuer uses a dedicated code gor getting metadata but the path.

51. By Natalia Bidart

Stringifying the metadata dict.

Revision history for this message
Facundo Batista (facundo) wrote :

Ok, so.

review: Approve
52. By Natalia Bidart

Merged trunk in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'data/tests'
2=== added file 'data/tests/metadata-test.txt'
3=== modified file 'data/ui/gui.glade'
4--- data/ui/gui.glade 2010-06-05 19:46:04 +0000
5+++ data/ui/gui.glade 2010-06-14 23:37:28 +0000
6@@ -257,6 +257,7 @@
7 </child>
8 <child>
9 <object class="GtkToolButton" id="raw_metadata">
10+ <property name="visible">True</property>
11 <property name="sensitive">False</property>
12 <property name="label" translatable="yes">Metadata</property>
13 <property name="use_underline">True</property>
14@@ -1051,4 +1052,130 @@
15 <action-widget response="0">shares_to_others_close</action-widget>
16 </action-widgets>
17 </object>
18+ <object class="GtkDialog" id="raw_metadata_dialog">
19+ <property name="width_request">600</property>
20+ <property name="height_request">300</property>
21+ <property name="border_width">5</property>
22+ <property name="title" translatable="yes">Raw metadata</property>
23+ <property name="window_position">center</property>
24+ <property name="type_hint">normal</property>
25+ <property name="has_separator">False</property>
26+ <signal name="close" handler="on_raw_metadata_close_clicked"/>
27+ <child internal-child="vbox">
28+ <object class="GtkVBox" id="dialog-vbox9">
29+ <property name="visible">True</property>
30+ <property name="spacing">2</property>
31+ <child>
32+ <object class="GtkTextView" id="raw_metadata_view">
33+ <property name="visible">True</property>
34+ <property name="can_focus">True</property>
35+ <property name="editable">False</property>
36+ <property name="wrap_mode">word</property>
37+ </object>
38+ <packing>
39+ <property name="position">1</property>
40+ </packing>
41+ </child>
42+ <child>
43+ <object class="GtkImage" id="raw_metadata_image">
44+ <property name="visible">True</property>
45+ <property name="stock">gtk-missing-image</property>
46+ </object>
47+ <packing>
48+ <property name="position">2</property>
49+ </packing>
50+ </child>
51+ <child internal-child="action_area">
52+ <object class="GtkHButtonBox" id="dialog-action_area9">
53+ <property name="visible">True</property>
54+ <property name="layout_style">end</property>
55+ <child>
56+ <object class="GtkButton" id="raw_metadata_close">
57+ <property name="label">gtk-close</property>
58+ <property name="visible">True</property>
59+ <property name="can_focus">True</property>
60+ <property name="receives_default">True</property>
61+ <property name="use_stock">True</property>
62+ <signal name="clicked" handler="on_raw_metadata_close_clicked"/>
63+ <signal name="activate" handler="on_raw_metadata_close_clicked"/>
64+ </object>
65+ <packing>
66+ <property name="expand">False</property>
67+ <property name="fill">False</property>
68+ <property name="position">0</property>
69+ </packing>
70+ </child>
71+ </object>
72+ <packing>
73+ <property name="expand">False</property>
74+ <property name="pack_type">end</property>
75+ <property name="position">0</property>
76+ </packing>
77+ </child>
78+ </object>
79+ </child>
80+ <action-widgets>
81+ <action-widget response="0">raw_metadata_close</action-widget>
82+ </action-widgets>
83+ </object>
84+ <object class="GtkFileChooserDialog" id="file_chooser">
85+ <property name="border_width">5</property>
86+ <property name="type_hint">normal</property>
87+ <property name="has_separator">False</property>
88+ <signal name="file_activated" handler="on_file_chooser_open_clicked"/>
89+ <child internal-child="vbox">
90+ <object class="GtkVBox" id="dialog-vbox7">
91+ <property name="visible">True</property>
92+ <property name="spacing">2</property>
93+ <child>
94+ <placeholder/>
95+ </child>
96+ <child internal-child="action_area">
97+ <object class="GtkHButtonBox" id="dialog-action_area7">
98+ <property name="visible">True</property>
99+ <property name="layout_style">end</property>
100+ <child>
101+ <object class="GtkButton" id="file_chooser_cancel">
102+ <property name="label">gtk-cancel</property>
103+ <property name="visible">True</property>
104+ <property name="can_focus">True</property>
105+ <property name="receives_default">True</property>
106+ <property name="use_stock">True</property>
107+ </object>
108+ <packing>
109+ <property name="expand">False</property>
110+ <property name="fill">False</property>
111+ <property name="position">0</property>
112+ </packing>
113+ </child>
114+ <child>
115+ <object class="GtkButton" id="file_chooser_open">
116+ <property name="label">gtk-open</property>
117+ <property name="visible">True</property>
118+ <property name="can_focus">True</property>
119+ <property name="receives_default">True</property>
120+ <property name="use_stock">True</property>
121+ <signal name="clicked" handler="on_file_chooser_open_clicked"/>
122+ <signal name="activate" handler="on_file_chooser_open_clicked"/>
123+ </object>
124+ <packing>
125+ <property name="expand">False</property>
126+ <property name="fill">False</property>
127+ <property name="position">1</property>
128+ </packing>
129+ </child>
130+ </object>
131+ <packing>
132+ <property name="expand">False</property>
133+ <property name="pack_type">end</property>
134+ <property name="position">0</property>
135+ </packing>
136+ </child>
137+ </object>
138+ </child>
139+ <action-widgets>
140+ <action-widget response="-6">file_chooser_cancel</action-widget>
141+ <action-widget response="0">file_chooser_open</action-widget>
142+ </action-widgets>
143+ </object>
144 </interface>
145
146=== modified file 'magicicada/__init__.py'
147--- magicicada/__init__.py 2010-06-08 21:14:28 +0000
148+++ magicicada/__init__.py 2010-06-14 23:37:28 +0000
149@@ -68,19 +68,26 @@
150
151 widgets = (
152 'start', 'stop', 'connect', 'disconnect', # toolbar buttons
153+
154 'folders', 'folders_dialog', # folders
155 'folders_store', 'folders_close',
156+
157 'shares_to_me', 'shares_to_me_dialog', # shares_to_me
158 'shares_to_me_store', 'shares_to_me_close',
159+
160 'shares_to_others', 'shares_to_others_dialog', # shares_to_others
161 'shares_to_others_store', 'shares_to_others_close',
162+
163+ 'raw_metadata', 'raw_metadata_dialog', # raw metadata
164+ 'raw_metadata_close', 'raw_metadata_view', 'raw_metadata_image',
165+
166 'is_started', 'is_connected', 'is_online', # status bar images
167 'status_label', 'status_icon', # status label and systray icon
168 'metaq_view', 'contentq_view', # queues tree views
169 'metaq_store', 'contentq_store', # queues list stores
170 'metaq_label', 'contentq_label', # queues labels
171- 'raw_metadata', # raw metadata
172 'about_dialog', # dialogs
173+ 'file_chooser', 'file_chooser_open', 'file_chooser_cancel',
174 'main_window'
175 )
176 for widget in widgets:
177@@ -111,11 +118,12 @@
178 self.sd.status_changed_callback = self.on_status_changed
179 self.sd.content_queue_changed_callback = self.on_content_queue_changed
180 self.sd.meta_queue_changed_callback = self.on_meta_queue_changed
181+ self.sd.on_metadata_ready_callback = self.on_metadata_ready
182
183 self.widget_is_visible = lambda w: w.get_property('visible')
184 self.widget_enabled = lambda w: self.widget_is_visible(w) and \
185 w.is_sensitive()
186-
187+ self.last_metadata_path = None
188 self.update()
189
190 # GTK callbacks
191@@ -145,6 +153,7 @@
192 """Stop syncdaemon."""
193 for v in self.volumes:
194 v.set_sensitive(False)
195+ self.raw_metadata.set_sensitive(False)
196
197 if self.widget_enabled(self.disconnect):
198 self.on_disconnect_clicked(self.disconnect)
199@@ -221,8 +230,27 @@
200 self.shares_to_others_store,
201 self.shares_to_others_dialog)
202
203+ def on_file_chooser_open_clicked(self, widget, data=None):
204+ """Close the file_chooser dialog."""
205+ self.file_chooser.response(gtk.FILE_CHOOSER_ACTION_OPEN)
206+
207+ def on_raw_metadata_close_clicked(self, widget, data=None):
208+ """Close the raw_metadata dialog."""
209+ self.raw_metadata_dialog.hide()
210+
211 def on_raw_metadata_clicked(self, widget, data=None):
212 """Show raw metadata for a path choosen by the user."""
213+ res = self.file_chooser.run()
214+ self.file_chooser.hide()
215+ if res != gtk.FILE_CHOOSER_ACTION_OPEN:
216+ return
217+
218+ self.last_metadata_path = self.file_chooser.get_filename()
219+ self.sd.get_metadata(path=self.last_metadata_path)
220+ self.raw_metadata_view.hide()
221+ self.raw_metadata_image.show()
222+ self._start_loading(self.raw_metadata_image)
223+ self.raw_metadata_dialog.show()
224
225 def on_status_icon_activate(self, widget, data=None):
226 """Systray icon was clicked."""
227@@ -243,6 +271,7 @@
228
229 for v in self.volumes:
230 v.set_sensitive(True)
231+ self.raw_metadata.set_sensitive(True)
232
233 self._update_queues_and_status(self.sd.current_state)
234
235@@ -332,6 +361,14 @@
236 highlight = state.processing_meta and state.is_online
237 self._on_queue_changed(META_QUEUE, items, highlight)
238
239+ def on_metadata_ready(self, path, metadata):
240+ """Lower layer has the requested metadata for 'path'."""
241+ if path != self.last_metadata_path:
242+ return
243+ self.raw_metadata_image.hide()
244+ self.raw_metadata_view.show()
245+ self.raw_metadata_view.get_buffer().set_text(str(metadata))
246+
247 # custom
248
249 def _start_loading(self, what):
250
251=== modified file 'magicicada/tests/test_magicicada.py'
252--- magicicada/tests/test_magicicada.py 2010-06-08 21:14:28 +0000
253+++ magicicada/tests/test_magicicada.py 2010-06-14 23:37:28 +0000
254@@ -28,20 +28,28 @@
255
256 from magicicada import MagicicadaUI, CONTENT_QUEUE, META_QUEUE, syncdaemon
257 from magicicada.dbusiface import QueueData, FolderData, ShareData
258-from magicicada.helpers import NO_OP, humanize_bytes
259+from magicicada.helpers import NO_OP, humanize_bytes, get_data_file
260
261 def process_gtk_pendings():
262 while gtk.events_pending(): gtk.main_iteration()
263
264 def close_dialog((dialog, test)):
265 """Call the 'test', close 'dialog'."""
266- try:
267- process_gtk_pendings()
268- test()
269- process_gtk_pendings()
270- finally:
271- dialog.response(gtk.RESPONSE_CLOSE)
272- process_gtk_pendings()
273+ response = gtk.RESPONSE_CLOSE
274+ try:
275+ process_gtk_pendings()
276+ test()
277+ finally:
278+ dialog.response(response)
279+ process_gtk_pendings()
280+ return False # do not be called again
281+
282+def test_and_click((button, test)):
283+ """Call the 'test', and click 'button'."""
284+ try:
285+ test()
286+ finally:
287+ button.clicked()
288 return False # do not be called again
289
290
291@@ -49,6 +57,8 @@
292 """A faked syncdaemon."""
293
294 def __init__(self):
295+ self._meta_path = None
296+
297 self.current_state = syncdaemon.State()
298 self.meta_queue = []
299 self.content_queue = []
300@@ -64,13 +74,15 @@
301 self.status_changed_callback = NO_OP
302 self.content_queue_changed_callback = NO_OP
303 self.meta_queue_changed_callback = NO_OP
304+ self.on_metadata_ready_callback = None # mandatory
305 self.shutdown = NO_OP
306
307 self.start = lambda: setattr(self.current_state, 'is_started', True)
308 self.quit = lambda: setattr(self.current_state, 'is_started', False)
309 self.connect = lambda: setattr(self.current_state, 'is_connected', True)
310- self.disconnect = \
311- lambda: setattr(self.current_state, 'is_connected', False)
312+ self.disconnect = lambda: \
313+ setattr(self.current_state, 'is_connected', False)
314+ self.get_metadata = lambda path: setattr(self, '_meta_path', path)
315
316
317 class MagicicadaUITestCase(TestCase):
318@@ -79,13 +91,13 @@
319 def setUp(self):
320 """Init."""
321 self.ui = MagicicadaUI(syncdaemon_class=FakedSyncdaemon)
322- self._called = False
323- self.set_called = lambda *args, **kwargs: setattr(self, '_called', True)
324+ self.called = False
325+ self.set_called = lambda *args, **kwargs: setattr(self, 'called', True)
326
327 def tearDown(self):
328 """Cleanup."""
329 self.ui.on_main_window_destroy(self.ui.main_window)
330- self._called = False
331+ self.called = False
332
333 def do_start(self):
334 """Simulate that start fully happened."""
335@@ -134,21 +146,34 @@
336
337 def assert_indicator_disabled(self, indicator):
338 """Test that 'indicator' is not sensitive."""
339- self.assertFalse(indicator.is_sensitive(), 'indicator is not sensitive')
340+ self.assertFalse(indicator.is_sensitive(),
341+ 'indicator must not be sensitive.')
342
343 def assert_indicator_ready(self, indicator):
344 """Test that 'indicator' is sensitive and green."""
345- self.assertTrue(indicator.is_sensitive(), 'indicator is sensitive')
346+ self.assertTrue(indicator.is_sensitive(),
347+ 'indicator must be sensitive.')
348 expected = indicator.get_pixbuf() # a test on its own
349 self.assertEqual(self.ui.active_indicator, expected,
350- 'indicator is the correct pixbuf')
351+ 'indicator must have the correct pixbuf.')
352
353 def assert_indicator_loading(self, indicator):
354 """Test that 'indicator' is sensitive and loading."""
355- self.assertTrue(indicator.is_sensitive(), 'indicator is sensitive')
356+ self.assertTrue(indicator.is_sensitive(),
357+ 'indicator must be sensitive.')
358 expected = indicator.get_animation() # a test on its own
359 self.assertEqual(self.ui.loading_animation, expected,
360- 'indicator is the correct animation')
361+ 'indicator must have the correct animation.')
362+
363+ def assert_widget_availability(self, widget_name, enabled=True):
364+ """Check button availability according to 'enabled'."""
365+ widget = getattr(self.ui, widget_name)
366+ self.assertTrue(self.ui.widget_is_visible(widget),
367+ '%s should be visible' % widget_name)
368+ sensitive = widget.is_sensitive()
369+ msg = '%s should %sbe sensitive'
370+ self.assertTrue(sensitive if enabled else not sensitive,
371+ msg % (widget_name, '' if enabled else 'not '))
372
373
374 class MagicicadaUIBasicTestCase(MagicicadaUITestCase):
375@@ -163,7 +188,7 @@
376 """SyncDaemon instance is shutdown at destroy time."""
377 self.patch(self.ui.sd, 'shutdown', self.set_called)
378 self.ui.on_main_window_destroy(self.ui.main_window)
379- self.assertTrue(self._called,
380+ self.assertTrue(self.called,
381 'syncdaemon.shutdown must be called at destroy time.')
382
383 def test_main_window_is_visible(self):
384@@ -198,7 +223,7 @@
385 """Update is called at startup."""
386 self.patch(MagicicadaUI, 'update', self.set_called)
387 self.ui = MagicicadaUI(syncdaemon_class=FakedSyncdaemon)
388- self.assertTrue(self._called,
389+ self.assertTrue(self.called,
390 'update was called at startup.')
391
392
393@@ -221,7 +246,7 @@
394 """Test on_start_clicked."""
395 self.patch(self.ui.sd, 'start', self.set_called)
396 self.ui.on_start_clicked(self.ui.start)
397- self.assertTrue(self._called, 'syncdaemon.start was called.')
398+ self.assertTrue(self.called, 'syncdaemon.start was called.')
399
400 def test_on_connect_clicked(self):
401 """Test on_connect_clicked."""
402@@ -240,7 +265,7 @@
403 """Test on_connect_clicked."""
404 self.patch(self.ui.sd, 'connect', self.set_called)
405 self.ui.on_connect_clicked(self.ui.connect)
406- self.assertTrue(self._called, 'syncdaemon.connect was called.')
407+ self.assertTrue(self.called, 'syncdaemon.connect was called.')
408
409 def test_on_stop_clicked(self):
410 """Test on_stop_clicked."""
411@@ -249,7 +274,7 @@
412 self.patch(self.ui, 'on_disconnect_clicked', self.set_called)
413 self.ui.on_stop_clicked(self.ui.stop)
414
415- self.assertFalse(self._called, 'on_disconnect_clicked was not called.')
416+ self.assertFalse(self.called, 'on_disconnect_clicked was not called.')
417
418 self.assertFalse(self.ui.widget_is_visible(self.ui.start))
419 self.assertTrue(self.ui.widget_is_visible(self.ui.stop))
420@@ -265,13 +290,13 @@
421 self.patch(self.ui, 'on_disconnect_clicked', self.set_called)
422 self.ui.on_stop_clicked(self.ui.stop)
423
424- self.assertTrue(self._called, 'on_disconnect_clicked was called.')
425+ self.assertTrue(self.called, 'on_disconnect_clicked was called.')
426
427 def test_on_stop_clicked_stops_syncdaemon(self):
428 """Test on_stop_clicked."""
429 self.patch(self.ui.sd, 'quit', self.set_called)
430 self.ui.on_stop_clicked(self.ui.stop)
431- self.assertTrue(self._called, 'syncdaemon.quit was called.')
432+ self.assertTrue(self.called, 'syncdaemon.quit was called.')
433
434 def test_on_disconnect_clicked(self):
435 """Test on_disconnect_clicked."""
436@@ -286,7 +311,7 @@
437 """Test on_disconnect_clicked."""
438 self.patch(self.ui.sd, 'disconnect', self.set_called)
439 self.ui.on_disconnect_clicked(self.ui.disconnect)
440- self.assertTrue(self._called, 'syncdaemon.disconnect was called.')
441+ self.assertTrue(self.called, 'syncdaemon.disconnect was called.')
442
443
444 class MagicicadaUISystrayIconTestCase(MagicicadaUITestCase):
445@@ -431,7 +456,7 @@
446 cb = 'on_%s_queue_changed' % self.name
447 self.patch(self.ui, cb, self.set_called)
448 self.ui.on_stopped()
449- self.assertTrue(self._called,
450+ self.assertTrue(self.called,
451 '%s was called on_stopped.' % cb)
452
453 @skip_abstract_class
454@@ -560,7 +585,7 @@
455 """Status callback is connected."""
456 self.assertEqual(self.ui.sd.status_changed_callback,
457 self.ui.on_status_changed,
458- 'status_changed callback must be set')
459+ 'status_changed callback must be set.')
460
461 def test_status_label_ellipsizes(self):
462 """The status label ellipsizes."""
463@@ -600,7 +625,7 @@
464 """On SD stoppped, the UI updates the status label."""
465 self.patch(self.ui, 'on_status_changed', self.set_called)
466 self.ui.on_stopped()
467- self.assertTrue(self._called,
468+ self.assertTrue(self.called,
469 'on_status_changed was called on_stopped.')
470
471 def test_status_label_default_if_not_started(self):
472@@ -767,6 +792,7 @@
473 self.volume_dialog_name = '%s_dialog' % self.name
474 self.volume_dialog = getattr(self.ui, self.volume_dialog_name)
475 self.on_volume_clicked = getattr(self.ui, 'on_%s_clicked' % self.name)
476+ self.volume_close = getattr(self.ui, '%s_close' % self.name)
477
478 def build_some_data(self, limit=5):
479 """Build some data to act as volume."""
480@@ -774,46 +800,42 @@
481 res = super(_MagicicadaUIVolumeTestCase, self).build_some_data(**kwargs)
482 return res
483
484- def assert_volume_availability(self, enabled):
485+ def assert_widget_availability(self, enabled=True):
486 """Check volume availability according to 'enabled'."""
487- self.assertTrue(self.ui.widget_is_visible(self.volume),
488- '%s should be visible' % self.name)
489- sensitive = self.volume.is_sensitive()
490- msg = '%s should %sbe sensitive'
491- self.assertTrue(sensitive if enabled else not sensitive,
492- msg % (self.name, '' if enabled else 'not '))
493+ s = super(_MagicicadaUIVolumeTestCase, self)
494+ s.assert_widget_availability(self.name, enabled)
495
496 @skip_abstract_class
497 def test_volume_are_disabled_until_started(self):
498 """Folders and shares are disabled until online."""
499 # disabled at startup
500- self.assert_volume_availability(enabled=False)
501+ self.assert_widget_availability(enabled=False)
502
503 # enabled when started
504 self.do_start()
505- self.assert_volume_availability(enabled=True)
506+ self.assert_widget_availability(enabled=True)
507
508 @skip_abstract_class
509 def test_volume_are_enabled_until_stopped(self):
510 """Folders and shares are enabled until offline."""
511 self.do_connect()
512- self.assert_volume_availability(enabled=True)
513+ self.assert_widget_availability(enabled=True)
514
515 self.ui.on_online()
516- self.assert_volume_availability(enabled=True)
517+ self.assert_widget_availability(enabled=True)
518
519 self.ui.on_offline()
520- self.assert_volume_availability(enabled=True)
521+ self.assert_widget_availability(enabled=True)
522
523 self.ui.on_disconnect_clicked(self.ui.disconnect)
524 self.ui.on_disconnected()
525- self.assert_volume_availability(enabled=True)
526+ self.assert_widget_availability(enabled=True)
527
528 # disabled when stopped
529 self.ui.on_stop_clicked(self.ui.stop)
530- self.assert_volume_availability(enabled=False)
531+ self.assert_widget_availability(enabled=False)
532 self.ui.on_stopped()
533- self.assert_volume_availability(enabled=False)
534+ self.assert_widget_availability(enabled=False)
535
536 @skip_abstract_class
537 def test_volume_close_emits_response_close(self):
538@@ -824,10 +846,9 @@
539 self.response = value
540 self.patch(self.volume_dialog, 'response', record_response)
541
542- volume_close = '%s_close' % self.name
543- getattr(self.ui, volume_close).clicked()
544+ self.volume_close.clicked()
545 self.assertEqual(gtk.RESPONSE_CLOSE, self.response,
546- '%s should emit RESPONSE_CLOSE.' % volume_close)
547+ 'volume close button should emit RESPONSE_CLOSE.')
548
549 @skip_abstract_class
550 def test_on_volume_clicked(self):
551@@ -882,7 +903,7 @@
552 self.on_volume_clicked(self.volume)
553
554 @skip_abstract_class
555- def test_volume_dialog_props(self):
556+ def test_volume_dialog_properties(self):
557 """The volume dialog has correct properties."""
558 size = self.volume_dialog.size_request()
559 self.assertEquals((600, 300), size)
560@@ -900,6 +921,7 @@
561 self.assertEqual(expected, actual,
562 msg % (self.volume_dialog_name, expected, actual))
563
564+
565 class MagicicadaUIFoldersTestCase(_MagicicadaUIVolumeTestCase):
566 """UI test cases for folders."""
567
568@@ -945,3 +967,203 @@
569
570 name = 'shares_to_others'
571
572+
573+class MagicicadaUIMetadataTestCase(MagicicadaUITestCase):
574+ """UI test cases for metadata display."""
575+
576+ name = 'raw_metadata'
577+
578+ def setUp(self):
579+ """Init."""
580+ super(MagicicadaUIMetadataTestCase, self).setUp()
581+ self.path = get_data_file('tests', 'metadata-test.txt')
582+ self.metadata = dict(bla='ble', foo='bar')
583+
584+ def assert_widget_availability(self, enabled=True):
585+ """Check button availability according to 'enabled'."""
586+ s = super(MagicicadaUIMetadataTestCase, self)
587+ s.assert_widget_availability(self.name, enabled)
588+
589+ def assert_dialog_visibility(self, dialog, text_view, image):
590+ """Check the visibility for dialog, text_view and image."""
591+ msg = '%s visibility should be %s (got %s instead).'
592+ visible = self.ui.widget_is_visible(self.ui.raw_metadata_dialog)
593+ self.assertEqual(dialog, visible,
594+ msg % ('raw_metadata_dialog', dialog, visible))
595+
596+ visible = self.ui.widget_is_visible(self.ui.raw_metadata_view)
597+ self.assertEqual(text_view, visible,
598+ msg % ('raw_metadata_view', text_view, visible))
599+
600+ visible = self.ui.widget_is_visible(self.ui.raw_metadata_image)
601+ self.assertEqual(image, visible,
602+ msg % ('raw_metadata_image', image, visible))
603+
604+ def test_raw_metadata_are_disabled_until_started(self):
605+ """Raw metadata button is disabled until online."""
606+ # disabled at startup
607+ self.assert_widget_availability(enabled=False)
608+
609+ # enabled when started
610+ self.do_start()
611+ self.assert_widget_availability(enabled=True)
612+
613+ def test_raw_metadata_are_enabled_until_stopped(self):
614+ """Raw metadata button is enabled until offline."""
615+ self.do_connect()
616+ self.assert_widget_availability(enabled=True)
617+
618+ self.ui.on_online()
619+ self.assert_widget_availability(enabled=True)
620+
621+ self.ui.on_offline()
622+ self.assert_widget_availability(enabled=True)
623+
624+ self.ui.on_disconnect_clicked(self.ui.disconnect)
625+ self.ui.on_disconnected()
626+ self.assert_widget_availability(enabled=True)
627+
628+ # disabled when stopped
629+ self.ui.on_stop_clicked(self.ui.stop)
630+ self.assert_widget_availability(enabled=False)
631+ self.ui.on_stopped()
632+ self.assert_widget_availability(enabled=False)
633+
634+ def test_raw_metadata_close_hides_the_dialog(self):
635+ """Test raw_metadata close button emits RESPONSE_CLOSE when clicked."""
636+ self.ui.raw_metadata_close.clicked()
637+ self.assertFalse(self.ui.widget_is_visible(self.ui.raw_metadata_dialog),
638+ 'raw_metadata_dialog should not be visible.')
639+
640+ def test_file_chooser_open_emits_response_ok(self):
641+ """Test volume close button emits RESPONSE_CLOSE when clicked."""
642+ self.response = None
643+ def record_response(value):
644+ """Record the response received."""
645+ self.response = value
646+ self.patch(self.ui.file_chooser, 'response', record_response)
647+
648+ self.ui.file_chooser_open.clicked()
649+ self.assertEqual(gtk.FILE_CHOOSER_ACTION_OPEN, self.response,
650+ 'open button should emit FILE_CHOOSER_ACTION_OPEN.')
651+
652+ def test_on_raw_metadata_clicked(self):
653+ """Test on_raw_metadata_clicked."""
654+ self.assertFalse(self.ui.widget_is_visible(self.ui.raw_metadata_dialog),
655+ 'raw_metadata_dialog should not be visible.')
656+
657+ self.ui.file_chooser.set_filename(self.path)
658+ def test_file_chooser():
659+ """Auxiliar to assert over the file_chooser."""
660+ self.assertTrue(self.ui.widget_is_visible(self.ui.file_chooser),
661+ 'file_chooser must be visible on metadata clicked.')
662+
663+ gobject.timeout_add(100, test_and_click,
664+ (self.ui.file_chooser_open, test_file_chooser))
665+ self.ui.on_raw_metadata_clicked(self.ui.raw_metadata)
666+
667+ self.assertFalse(self.ui.widget_is_visible(self.ui.file_chooser),
668+ 'file_chooser must be visible after metadata clicked.')
669+ self.assertEqual(self.path, self.ui.file_chooser.get_filename(),
670+ 'filename returned by file chooser must be correct.')
671+
672+ # raw_metadata_dialog is enabled and shows the loading animation
673+ self.assert_dialog_visibility(dialog=True, text_view=False, image=True)
674+ expected = self.ui.raw_metadata_image.get_animation()
675+ self.assertEqual(self.ui.loading_animation, expected,
676+ 'raw_metadata_image must have the correct animation.')
677+
678+ # Check that the metadata was asked to the SD
679+ self.assertEqual(self.ui.sd._meta_path, self.path)
680+ # SD will eventually callback us with the metadata
681+ self.ui.on_metadata_ready(self.path, self.metadata)
682+
683+ # raw_metadata_dialog is enabled and shows the metadata
684+ self.assert_dialog_visibility(dialog=True, text_view=True, image=False)
685+
686+ # user closes the dialog
687+ self.ui.raw_metadata_close.clicked()
688+
689+ # dialog was closed already
690+ self.assertFalse(self.ui.widget_is_visible(self.ui.raw_metadata_dialog),
691+ 'raw_metadata_dialog should not be visible.')
692+
693+ def test_raw_metadata_dialog_properties(self):
694+ """The raw_metadata dialog has correct properties."""
695+ dialog = 'raw_metadata_dialog'
696+ size = self.ui.raw_metadata_dialog.size_request()
697+ self.assertEquals((600, 300), size)
698+
699+ self.assertFalse(self.ui.raw_metadata_dialog.get_modal(),
700+ '%s must not be modal.' % dialog)
701+
702+ position = self.ui.raw_metadata_dialog.get_property('window-position')
703+ self.assertEqual(gtk.WIN_POS_CENTER, position,
704+ '%s must be centered.' % dialog)
705+
706+ actual = self.ui.raw_metadata_dialog.get_title()
707+ expected = self.name.replace('_', ' ').capitalize()
708+ msg = '%s title must be %s (got %s instead)'
709+ self.assertEqual(expected, actual, msg % (dialog, expected, actual))
710+
711+ actual = self.ui.raw_metadata_view.get_wrap_mode()
712+ msg = 'wrap mode for view must be gtk.WRAP_WORD (got %s instead).'
713+ self.assertEqual(gtk.WRAP_WORD, actual, msg % actual)
714+
715+ def test_callback_is_connected(self):
716+ """Metadata ready callback is connected."""
717+ self.assertEqual(self.ui.sd.on_metadata_ready_callback,
718+ self.ui.on_metadata_ready,
719+ 'on_metadata_ready_callback callback must be set.')
720+
721+ def test_file_chooser_is_hidden_at_startup(self):
722+ """File chooser exists but is not visible."""
723+ self.assertFalse(self.ui.widget_is_visible(self.ui.file_chooser),
724+ 'file_chooser must be hidden by default.')
725+
726+ def test_filename_is_used_only_if_open_clicked(self):
727+ """Filename is used only if user clicked open."""
728+ self.patch(self.ui.sd, 'get_metadata', self.set_called)
729+ gobject.timeout_add(100, test_and_click,
730+ (self.ui.file_chooser_cancel, NO_OP))
731+ self.ui.on_raw_metadata_clicked(self.ui.raw_metadata)
732+
733+ self.assertFalse(self.called,
734+ 'get_metadata should not be called if no file chosen.')
735+
736+ def test_filename_is_stored_if_open_clicked(self):
737+ """Filename is stored as 'last_metadata_path' if user clicked open."""
738+ self.assertTrue(self.ui.last_metadata_path is None,
739+ 'last_metadata_path must be None.')
740+ self.ui.file_chooser.set_filename(self.path)
741+ gobject.timeout_add(100, test_and_click,
742+ (self.ui.file_chooser_open, NO_OP))
743+ self.ui.on_raw_metadata_clicked(self.ui.raw_metadata)
744+
745+ self.assertEqual(self.path, self.ui.last_metadata_path,
746+ 'last_metadata_path should be what the user choose.')
747+
748+ def test_on_metadata_ready(self):
749+ """Callback on_metadata_ready updates the raw_metadata_view."""
750+ path = 'bla'
751+ self.ui.last_metadata_path = path
752+ self.ui.on_metadata_ready(path, self.metadata)
753+
754+ buffer = self.ui.raw_metadata_view.get_buffer()
755+ self.assertTrue(buffer is not None,
756+ 'buffer for raw_metadata_view must not be None.')
757+ actual = buffer.get_text(*buffer.get_bounds())
758+ msg = 'buffer content must be %s (got %s instead).'
759+ self.assertEqual(actual, str(self.metadata),
760+ msg % (self.metadata, actual))
761+
762+ def test_on_metadata_ready_doesnt_update_if_last_path_doesnt_match(self):
763+ """Callback on_metadata_ready updates the raw_metadata_view."""
764+ self.patch(self.ui.raw_metadata_view.get_buffer(),
765+ 'set_text', self.set_called)
766+ path = 'bla'
767+ self.ui.last_metadata_path = path+path
768+ self.ui.on_metadata_ready(path, self.metadata)
769+
770+ self.assertFalse(self.called,
771+ 'view should not be updated if key is not last one.')

Subscribers

People subscribed via source and target branches

to all changes: