1
2# Copyright 2009-2017 Jaap Karssenberg <jaap.karssenberg@gmail.com>
3
4import tests
5
6from gi.repository import Gtk
7from gi.repository import Pango
8
9from zim.notebook import Path
10from zim.notebook.index.pages import MyTreeIter, IndexNotFoundError
11from zim.formats import ParseTree
12from zim.gui.clipboard import Clipboard
13
14from zim.plugins import find_extension, PluginManager
15from zim.plugins.pageindex import *
16
17
18from tests.mainwindow import setUpMainWindow
19
20
21class TestPageIndexPlugin(tests.TestCase):
22
23	def setUp(self):
24		self.plugin = PluginManager.load_plugin('pageindex')
25		self.window = setUpMainWindow(self.setUpNotebook(content=tests.FULL_NOTEBOOK))
26		self.extension = find_extension(self.window.pageview, PageIndexNotebookViewExtension)
27		assert self.extension is not None
28
29	def do_expandcollapse(self, autoexpand, autocollapse):
30		self.plugin.preferences.update({'autoexpand': autoexpand, 'autocollapse': autocollapse})
31		treeview = self.extension.treeview
32		treepath = treeview.get_model().find(Path('Test:foo'))
33
34		treeview.collapse_all()
35		self.assertFalse(treeview.row_expanded(treepath))
36
37		self.window.open_page(Path('Test:foo:bar'))
38		if autoexpand:
39			self.assertTrue(treeview.row_expanded(treepath))
40		else:
41			self.assertFalse(treeview.row_expanded(treepath))
42
43		self.window.open_page(Path('Test'))
44		if autoexpand and not autocollapse:
45			self.assertTrue(treeview.row_expanded(treepath))
46		else:
47			self.assertFalse(treeview.row_expanded(treepath))
48
49	def testAutoExpandAndCollapse(self):
50		self.do_expandcollapse(True, True)
51
52	def testNoAutoExpand(self):
53		self.do_expandcollapse(True, False)
54
55	def testAutoExpandNoCollapse(self):
56		self.do_expandcollapse(False, False)
57
58
59def init_model_validator_wrapper(test, model):
60
61		def validate_path_iter(model, path, iter):
62			assert isinstance(path, Gtk.TreePath)
63			assert model.iter_is_valid(iter)
64
65			test.assertEqual(model.get_path(iter).get_indices(), path.get_indices())
66
67			if model.iter_has_child(iter):
68				test.assertTrue(model.iter_n_children(iter) > 0)
69				child = model.iter_children(iter)
70				test.assertIsNotNone(child)
71				childpath = model.get_path(child)
72				test.assertEqual(childpath.get_indices(), path.get_indices() + [0])
73			else:
74				test.assertTrue(model.iter_n_children(iter) == 0)
75
76		def validate_parent_path_iter(model, path):
77			assert isinstance(path, Gtk.TreePath)
78			if path.get_depth() > 1:
79				parent = Gtk.TreePath(path.get_indices()[:-1])
80				iter = model.get_iter(parent)
81				validate_path_iter(model, parent, iter)
82
83		for signal in ('row-inserted', 'row-changed', 'row-has-child-toggled'):
84			model.connect(signal, validate_path_iter)
85
86		for signal in ('row-deleted',):
87			model.connect(signal, validate_parent_path_iter)
88
89
90class TestPageTreeStore(tests.TestCase):
91
92	def runTest(self):
93		notebook = self.setUpNotebook(content=tests.FULL_NOTEBOOK)
94
95		treestore = PageTreeStore(notebook.index)
96		init_model_validator_wrapper(self, treestore)
97		self.assertEqual(treestore.get_flags(), 0)
98		self.assertEqual(treestore.get_n_columns(), 7)
99		for i in range(treestore.get_n_columns()):
100			self.assertTrue(not treestore.get_column_type(i) is None)
101
102		n = treestore.on_iter_n_children(None)
103		self.assertTrue(n > 0)
104		n = treestore.iter_n_children(None)
105		self.assertTrue(n > 0)
106
107		# Quick check for basic methods
108		iter = treestore.on_get_iter((0,))
109		self.assertTrue(isinstance(iter, MyTreeIter))
110		self.assertFalse(iter.row['name'] == '')
111		self.assertEqual(iter.treepath, Gtk.TreePath((0,)))
112		self.assertEqual(treestore.on_get_path(iter), Gtk.TreePath((0,)))
113		self.assertEqual(treestore.find(Path(iter.row['name'])), Gtk.TreePath((0,)))
114		basename = treestore.on_get_value(iter, 0)
115		self.assertTrue(len(basename) > 0)
116
117		iter2 = treestore.on_iter_children(None)
118		self.assertIs(iter2, iter)
119
120		self.assertIsNone(treestore.on_get_iter((20, 20, 20, 20, 20)))
121		self.assertRaises(IndexNotFoundError, treestore.find, Path('nonexisting'))
122		self.assertRaises(ValueError, treestore.find, Path(':'))
123
124		# Now walk through the whole notebook testing the API
125		# with nested pages and stuff
126		npages = 0
127		path = []
128		for page in notebook.pages.walk():
129			#~ print('>>', page)
130			npages += 1
131			names = page.name.split(':')
132			if len(names) > len(path):
133				path.append(0) # always increment by one
134			elif len(names) < len(path):
135				while len(names) < len(path):
136					path.pop()
137				path[-1] += 1
138			else:
139				path[-1] += 1
140			#~ print('>>', page, path)
141			iter = treestore.get_iter(tuple(path))
142			indexpath = treestore.get_indexpath(iter)
143			#~ print('>>>', indexpath)
144			self.assertEqual(indexpath, page)
145			self.assertEqual(treestore.get_value(iter, NAME_COL), page.basename)
146			self.assertEqual(treestore.get_value(iter, PATH_COL), page)
147			if not treestore.get_value(iter, EXISTS_COL):
148				self.assertEqual(treestore.get_value(iter, STYLE_COL), Pango.Style.ITALIC)
149			else:
150				self.assertEqual(treestore.get_value(iter, STYLE_COL), Pango.Style.NORMAL)
151			self.assertEqual(treestore.get_path(iter), Gtk.TreePath(path))
152
153			if indexpath.haschildren:
154				self.assertTrue(treestore.iter_has_child(iter))
155				child = treestore.iter_children(iter)
156				self.assertTrue(not child is None)
157				child = treestore.iter_nth_child(iter, 0)
158				self.assertTrue(not child is None)
159				parent = treestore.iter_parent(child)
160				self.assertEqual(
161					treestore.get_indexpath(parent), page)
162				childpath = treestore.get_path(child)
163				self.assertEqual(
164					childpath, Gtk.TreePath(tuple(path) + (0,)))
165				n = treestore.iter_n_children(iter)
166				for i in range(1, n):
167					child = treestore.iter_next(child)
168					childpath = treestore.get_path(child)
169					self.assertEqual(
170						childpath, Gtk.TreePath(tuple(path) + (i,)))
171				child = treestore.iter_next(child)
172				self.assertIsNone(child) # children exhausted
173
174			else:
175				self.assertTrue(not treestore.iter_has_child(iter))
176				child = treestore.iter_children(iter)
177				self.assertTrue(child is None)
178				child = treestore.iter_nth_child(iter, 0)
179				self.assertTrue(child is None)
180
181		self.assertTrue(npages > 10) # double check sanity of walk() method
182
183
184class TestSignals(tests.TestCase):
185
186	PAGES = ('a', 'a:a', 'a:b', 'b', 'c')
187
188	def runTest(self):
189		notebook = self.setUpNotebook()
190		navigation = tests.MockObject(methods=('open_page',))
191		model = PageTreeStore(notebook.index)
192		init_model_validator_wrapper(self, model)
193		treeview = PageTreeView(notebook, navigation, model=model)
194
195		signals = []
196		def signal_logger(o, *a):
197			path = a[0].to_string()
198			signal = a[-1]
199			signals.append((signal, path))
200			#print(">>>", signal, path)
201
202		for signal in ('row-inserted', 'row-changed', 'row-deleted', 'row-has-child-toggled'):
203			model.connect(signal, signal_logger, signal)
204
205		for path in map(Path, self.PAGES):
206			page = notebook.get_page(path)
207			page.parse('plain', 'Test 123\n')
208			notebook.store_page(page)
209
210		expect_add = [
211			('row-inserted', '0'),
212			('row-changed', '0'),
213			('row-inserted', '0:0'),
214			('row-has-child-toggled', '0'),
215			('row-changed', '0'),
216			('row-changed', '0:0'),
217			('row-inserted', '0:1'),
218			('row-changed', '0'),
219			('row-changed', '0:1'),
220			('row-inserted', '1'),
221			('row-changed', '1'),
222			('row-inserted', '2'),
223			('row-changed', '2')
224		]
225		self.assertEqual(signals, expect_add)
226		signals[:] = []
227
228		for path in reversed(self.PAGES):
229			notebook.delete_page(Path(path))
230
231		expect_del = [
232			('row-deleted', '2'),
233			('row-deleted', '1'),
234			('row-deleted', '0:1'),
235			('row-changed', '0'),
236			('row-deleted', '0:0'),
237			('row-has-child-toggled', '0'),
238			('row-changed', '0'),
239			('row-deleted', '0')
240		]
241		self.assertEqual(signals, expect_del)
242
243
244class TestPageTreeView(tests.TestCase):
245
246	def setUp(self):
247		self.notebook = self.setUpNotebook(content=tests.FULL_NOTEBOOK)
248		navigation = tests.MockObject(methods=('open_page',))
249		self.model = PageTreeStore(self.notebook.index)
250		init_model_validator_wrapper(self, self.model)
251		self.treeview = PageTreeView(self.notebook, navigation, model=self.model)
252		treepath = self.treeview.set_current_page(Path('Test'))
253		assert treepath is not None
254		self.treeview.select_treepath(treepath)
255
256	def testView(self):
257		path = Path('Test:foo')
258		self.treeview.set_current_page(path)
259		# TODO assert something
260		treepath = self.treeview.get_model().find(path)
261		self.assertTrue(not treepath is None)
262		col = self.treeview.get_column(0)
263		self.treeview.row_activated(treepath, col)
264
265		#~ self.treeview.emit('popup-menu')
266		self.treeview.emit('insert-link', path)
267		#self.treeview.emit('copy')
268
269	def testContextMenuExpandCollapse(self):
270		menu = self.treeview.get_popup()
271
272		# Check these do not cause errors - how to verify state ?
273		tests.gtk_activate_menu_item(menu, _("Expand _All"))
274		tests.gtk_activate_menu_item(menu, _("_Collapse All"))
275
276	#@tests.expectedFailure
277	#def testContextMenuCopyLocation(self):
278	#	menu = self.treeview.get_popup()
279	#
280	#	# Copy item
281	#	tests.gtk_activate_menu_item(menu, 'gtk-copy')
282	#	self.assertEqual(Clipboard.get_text(), 'Test')
283
284	def testSignals(self):
285		pages = []
286
287		self.assertGreater(self.model.on_iter_n_children(None), 0)
288
289		# delete all pages
290		for path in list(self.notebook.pages.walk_bottomup()):
291			page = self.notebook.get_page(path)
292			pages.append((page.name, page.dump('wiki')))
293			self.notebook.delete_page(page)
294
295		self.assertEqual(self.notebook.pages.n_list_pages(), 0)
296		self.assertEqual(self.model.on_iter_n_children(None), 0)
297		# TODO: assert something on the view ?
298
299		# and add them again
300		for name, content in reversed(pages):
301			page = self.notebook.get_page(Path(name))
302			page.parse('wiki', content)
303			self.notebook.store_page(page)
304
305		self.assertGreater(self.model.on_iter_n_children(None), 0)
306		# TODO: assert something on the view ?
307
308	def testRestoreExpand(self):
309		treeview = self.treeview
310		treepath = treeview.get_model().find(Path('Test:foo'))
311		parent = treepath.copy()
312		parent.up()
313		treeview.collapse_all()
314
315		self.assertIsNone(treeview.get_expanded_path(treepath))
316		self.assertFalse(treeview.row_expanded(parent))
317
318		treeview.expand_to_path(parent)
319		self.assertEqual(treeview.get_expanded_path(treepath).to_string(), parent.to_string())
320		self.assertTrue(treeview.row_expanded(parent))
321		self.assertFalse(treeview.row_expanded(treepath))
322
323		treeview.expand_to_path(treepath)
324		self.assertEqual(treeview.get_expanded_path(treepath).to_string(), treepath.to_string())
325		self.assertTrue(treeview.row_expanded(treepath))
326
327		treeview._restore_expanded_path(treepath, treepath)
328		self.assertTrue(treeview.row_expanded(treepath))
329
330		treeview._restore_expanded_path(treepath, parent)
331		self.assertTrue(treeview.row_expanded(parent))
332		self.assertFalse(treeview.row_expanded(treepath))
333
334		treeview._restore_expanded_path(treepath, None)
335		self.assertFalse(treeview.row_expanded(parent))
336
337	def testDragAndDropCallbacks(self):
338		# Don't know how to test real drag and drop, but at least test the callbacks
339		self._testDragAndDropCallbacks(workaround=False)
340
341	def testDragAndDropCallbacksWithWorkaround(self):
342		# Testing work around for issue #390
343		self._testDragAndDropCallbacks(workaround=True)
344
345	def _testDragAndDropCallbacks(self, workaround):
346		treeview = self.treeview
347
348		mocktarget = tests.MockObject(return_values={'name': PAGELIST_TARGET_NAME})
349		mockselectiondata = tests.MockObject(return_values={'get_target': mocktarget, 'set': None})
350
351		treeview.do_drag_data_get(None, mockselectiondata, None, None)
352		self.assertEqual(mockselectiondata.lastMethodCall, ('set', mocktarget, 8, b'testnotebook?Test\r\n'))
353
354		if workaround:
355			self.assertEqual(zim.gui.clipboard._internal_selection_data, b'testnotebook?Test\r\n')
356			mockselectiondata.addMockMethod('get_data', None)
357		else:
358			zim.gui.clipboard._internal_selection_data = None
359			mockselectiondata.addMockMethod('get_data', b'testnotebook?Test\r\n')
360
361		treepath = treeview.get_model().find(Path('Foo'))
362		position = Gtk.TreeViewDropPosition.INTO_OR_BEFORE
363		treeview.get_dest_row_at_pos = lambda x, y: (treepath, position) # MOCK method
364
365		with tests.LoggingFilter('zim.notebook', message='Number of links after move'):
366			context = tests.MockObject(methods=('finish',))
367			treeview.do_drag_data_received(context, None, None, mockselectiondata, None, None)
368