1- # pylint: disable=no-self-use,protected-access
1+ # pylint: disable=no-self-use,protected-access,invalid-name,arguments-differ
22import unittest
33from unittest import mock
4- from contextlib import ExitStack
4+ from contextlib import ExitStack , contextmanager
55
66import os
77import io
88import csv
99import json
10+ from typing import Type , TypeVar , Optional
1011
1112import numpy as np
1213from numpy .testing import assert_array_equal
1314
14- from AnyQt .QtCore import QSettings
15+ from AnyQt .QtCore import QSettings , Qt
16+ from AnyQt .QtGui import QIcon
17+ from AnyQt .QtWidgets import QFileDialog
18+ from AnyQt .QtTest import QSignalSpy
19+
20+ from orangewidget .tests .utils import simulate
21+ from orangewidget .widget import OWBaseWidget
1522
1623from Orange .data import DiscreteVariable , TimeVariable , ContinuousVariable , \
1724 StringVariable
1825from Orange .tests import named_file
1926from Orange .widgets .tests .base import WidgetTest , GuiTest
2027from Orange .widgets .data import owcsvimport
2128from Orange .widgets .data .owcsvimport import (
22- pandas_to_table , ColumnType , RowSpec
29+ OWCSVFileImport , pandas_to_table , ColumnType , RowSpec ,
2330)
31+ from Orange .widgets .utils .pathutils import PathItem , samepath
2432from Orange .widgets .utils .settings import QSettings_writeArray
2533from Orange .widgets .utils .state_summary import format_summary_details
2634
35+ W = TypeVar ("W" , bound = OWBaseWidget )
36+
2737
2838class TestOWCSVFileImport (WidgetTest ):
39+ def create_widget (
40+ self , cls : Type [W ], stored_settings : Optional [dict ] = None ,
41+ reset_default_settings = True , ** kwargs ) -> W :
42+ if reset_default_settings :
43+ self .reset_default_settings (cls )
44+ widget = cls .__new__ (cls , signal_manager = self .signal_manager ,
45+ stored_settings = stored_settings , ** kwargs )
46+ widget .__init__ ()
47+
48+ def delete ():
49+ widget .onDeleteWidget ()
50+ widget .close ()
51+ widget .deleteLater ()
52+
53+ self ._stack .callback (delete )
54+ return widget
55+
2956 def setUp (self ):
57+ super ().setUp ()
3058 self ._stack = ExitStack ().__enter__ ()
3159 # patch `_local_settings` to avoid side effects, across tests
3260 fname = self ._stack .enter_context (named_file ("" ))
@@ -37,10 +65,9 @@ def setUp(self):
3765 self .widget = self .create_widget (owcsvimport .OWCSVFileImport )
3866
3967 def tearDown (self ):
40- self .widgets .remove (self .widget )
41- self .widget .onDeleteWidget ()
42- self .widget = None
68+ del self .widget
4369 self ._stack .close ()
70+ super ().tearDown ()
4471
4572 def test_basic (self ):
4673 w = self .widget
@@ -58,6 +85,8 @@ def test_basic(self):
5885 (range (1 , 3 ), RowSpec .Skipped ),
5986 ],
6087 )
88+ data_regions_path = os .path .join (
89+ os .path .dirname (__file__ ), "data-regions.tab" )
6190
6291 def _check_data_regions (self , table ):
6392 self .assertEqual (len (table ), 3 )
@@ -82,7 +111,7 @@ def test_restore(self):
82111 }
83112 )
84113 item = w .current_item ()
85- self .assertEqual ( item .path (), path )
114+ self .assertTrue ( samepath ( item .path (), path ) )
86115 self .assertEqual (item .options (), self .data_regions_options )
87116 out = self .get_output ("Data" , w )
88117 self ._check_data_regions (out )
@@ -102,12 +131,19 @@ def test_restore_from_local(self):
102131 owcsvimport .OWCSVFileImport ,
103132 )
104133 item = w .current_item ()
105- self .assertEqual (item .path (), path )
134+ self .assertIsNone (item )
135+ simulate .combobox_activate_index (w .recent_combo , 0 )
136+ item = w .current_item ()
137+ self .assertTrue (samepath (item .path (), path ))
106138 self .assertEqual (item .options (), self .data_regions_options )
139+ data = w .settingsHandler .pack_data (w )
107140 self .assertEqual (
108- w ._session_items , [(path , self .data_regions_options .as_dict ())],
109- "local settings item must be recorded in _session_items when "
110- "activated in __init__" ,
141+ data ['_session_items_v2' ], [
142+ (PathItem .AbsPath (path ).as_dict (),
143+ self .data_regions_options .as_dict ())
144+ ],
145+ "local settings item must be recorded in _session_items_v2 when "
146+ "activated" ,
111147 )
112148 self ._check_data_regions (self .get_output ("Data" , w ))
113149
@@ -189,6 +225,134 @@ def test_backward_compatibility(self):
189225 self .assertIsInstance (domain ["numeric2" ], ContinuousVariable )
190226 self .assertIsInstance (domain ["string" ], StringVariable )
191227
228+ @staticmethod
229+ @contextmanager
230+ def _browse_setup (widget : OWCSVFileImport , path : str ):
231+ browse_dialog = widget ._browse_dialog
232+ with mock .patch .object (widget , "_browse_dialog" ) as r :
233+ dlg = browse_dialog ()
234+ dlg .setOption (QFileDialog .DontUseNativeDialog )
235+ dlg .selectFile (path )
236+ dlg .exec_ = dlg .exec = lambda : QFileDialog .Accepted
237+ r .return_value = dlg
238+ with mock .patch .object (owcsvimport .CSVImportDialog , "exec_" ,
239+ lambda _ : QFileDialog .Accepted ):
240+ yield
241+
242+ def test_browse (self ):
243+ widget = self .widget
244+ path = self .data_regions_path
245+ with self ._browse_setup (widget , path ):
246+ widget .browse ()
247+ cur = widget .current_item ()
248+ self .assertIsNotNone (cur )
249+ self .assertTrue (samepath (cur .path (), path ))
250+
251+ def test_browse_prefix (self ):
252+ widget = self .widget
253+ path = self .data_regions_path
254+ with self ._browse_setup (widget , path ):
255+ basedir = os .path .dirname (__file__ )
256+ widget .workflowEnv = lambda : {"basedir" : basedir }
257+ widget .workflowEnvChanged ("basedir" , basedir , "" )
258+ widget .browse_relative (prefixname = "basedir" )
259+
260+ cur = widget .current_item ()
261+ self .assertIsNotNone (cur )
262+ self .assertTrue (samepath (cur .path (), path ))
263+ self .assertIsInstance (cur .varPath (), PathItem .VarPath )
264+
265+ def test_browse_prefix_parent (self ):
266+ widget = self .widget
267+ path = self .data_regions_path
268+
269+ with self ._browse_setup (widget , path ):
270+ basedir = os .path .join (os .path .dirname (__file__ ), "bs" )
271+ widget .workflowEnv = lambda : {"basedir" : basedir }
272+ widget .workflowEnvChanged ("basedir" , basedir , "" )
273+ mb = widget ._path_must_be_relative_mb = mock .Mock ()
274+ widget .browse_relative (prefixname = "basedir" )
275+ mb .assert_called ()
276+ self .assertIsNone (widget .current_item ())
277+
278+ def test_browse_for_missing (self ):
279+ missing = os .path .dirname (__file__ ) + "/this file does not exist.csv"
280+ widget = self .create_widget (
281+ owcsvimport .OWCSVFileImport , stored_settings = {
282+ "_session_items" : [
283+ (missing , self .data_regions_options .as_dict ())
284+ ]
285+ }
286+ )
287+ widget .activate_recent (0 )
288+ dlg = widget .findChild (QFileDialog )
289+ assert dlg is not None
290+ # calling selectFile when using native (macOS) dialog does not have
291+ # an effect - at least not immediately;
292+ dlg .setOption (QFileDialog .DontUseNativeDialog )
293+ dlg .selectFile (self .data_regions_path )
294+ dlg .accept ()
295+ cur = widget .current_item ()
296+ self .assertTrue (samepath (self .data_regions_path , cur .path ()))
297+ self .assertEqual (
298+ self .data_regions_options .as_dict (), cur .options ().as_dict ()
299+ )
300+
301+ def test_browse_for_missing_prefixed (self ):
302+ path = self .data_regions_path
303+ basedir = os .path .dirname (path )
304+ widget = self .create_widget (
305+ owcsvimport .OWCSVFileImport , stored_settings = {
306+ "__version__" : 3 ,
307+ "_session_items_v2" : [
308+ (PathItem .VarPath ("basedir" , "this file does not exist.csv" ).as_dict (),
309+ self .data_regions_options .as_dict ())]
310+ },
311+ env = {"basedir" : basedir }
312+ )
313+ widget .activate_recent (0 )
314+ dlg = widget .findChild (QFileDialog )
315+ assert dlg is not None
316+ # calling selectFile when using native (macOS) dialog does not have
317+ # an effect - at least not immediately;
318+ dlg .setOption (QFileDialog .DontUseNativeDialog )
319+ dlg .selectFile (path )
320+ dlg .accept ()
321+ cur = widget .current_item ()
322+ self .assertTrue (samepath (path , cur .path ()))
323+ self .assertEqual (
324+ cur .varPath (), PathItem .VarPath ("basedir" , "data-regions.tab" ))
325+ self .assertEqual (
326+ self .data_regions_options .as_dict (), cur .options ().as_dict ()
327+ )
328+
329+ def test_browse_for_missing_prefixed_parent (self ):
330+ path = self .data_regions_path
331+ basedir = os .path .join (os .path .dirname (path ), "origin1" )
332+ item = (PathItem .VarPath ("basedir" ,
333+ "this file does not exist.csv" ),
334+ self .data_regions_options )
335+ widget = self .create_widget (
336+ owcsvimport .OWCSVFileImport , stored_settings = {
337+ "__version__" : 3 ,
338+ "_session_items_v2" : [(item [0 ].as_dict (), item [1 ].as_dict ())]
339+ },
340+ env = {"basedir" : basedir }
341+ )
342+ mb = widget ._path_must_be_relative_mb = mock .Mock ()
343+ widget .activate_recent (0 )
344+ dlg = widget .findChild (QFileDialog )
345+ assert dlg is not None
346+ # calling selectFile when using native (macOS) dialog does not have
347+ # an effect - at least not immediately;
348+ dlg .setOption (QFileDialog .DontUseNativeDialog )
349+ dlg .selectFile (path )
350+ dlg .accept ()
351+ mb .assert_called ()
352+ cur = widget .current_item ()
353+ self .assertEqual (item [0 ], cur .varPath ())
354+ self .assertEqual (item [1 ].as_dict (), cur .options ().as_dict ())
355+
192356
193357class TestImportDialog (GuiTest ):
194358 @staticmethod
@@ -219,6 +383,42 @@ def test_dialog():
219383 opts1 = d .options ()
220384
221385
386+ class TestModel (GuiTest ):
387+ def test_model (self ):
388+ path = TestOWCSVFileImport .data_regions_path
389+ model = owcsvimport .VarPathItemModel ()
390+ model .setItemPrototype (owcsvimport .ImportItem ())
391+ it1 = owcsvimport .ImportItem ()
392+ it1 .setVarPath (PathItem .VarPath ("prefix" , "data-regions.tab" ))
393+ it2 = owcsvimport .ImportItem ()
394+ it2 .setVarPath (PathItem .AbsPath (path ))
395+ model .appendRow ([it1 ])
396+ model .appendRow ([it2 ])
397+
398+ def data (row , role ):
399+ return model .data (model .index (row , 0 ), role )
400+
401+ self .assertIsInstance (data (0 , Qt .DecorationRole ), QIcon )
402+ self .assertIsInstance (data (1 , Qt .DecorationRole ), QIcon )
403+
404+ self .assertEqual (data (0 , Qt .DisplayRole ), "data-regions.tab" )
405+ self .assertEqual (data (1 , Qt .DisplayRole ), "data-regions.tab" )
406+
407+ self .assertEqual (data (0 , Qt .ToolTipRole ), "${prefix}/data-regions.tab (missing)" )
408+ self .assertTrue (samepath (data (1 , Qt .ToolTipRole ), path ))
409+
410+ self .assertIsNotNone (data (0 , Qt .ForegroundRole ))
411+ self .assertIsNone (data (1 , Qt .ForegroundRole ))
412+ spy = QSignalSpy (model .dataChanged )
413+ model .setReplacementEnv ({"prefix" : os .path .dirname (path )})
414+ self .assertSequenceEqual (
415+ [[model .index (0 , 0 ), model .index (1 , 0 ), []]],
416+ list (spy )
417+ )
418+ self .assertEqual (data (0 , Qt .ToolTipRole ), "${prefix}/data-regions.tab" )
419+ self .assertIsNone (data (0 , Qt .ForegroundRole ))
420+
421+
222422class TestUtils (unittest .TestCase ):
223423 def test_load_csv (self ):
224424 contents = (
@@ -347,6 +547,14 @@ def test_open_compressed(self):
347547 with owcsvimport ._open (fname , "rt" , encoding = "ascii" ) as f :
348548 self .assertEqual (content , f .read ())
349549
550+ def test_sniff_csv (self ):
551+ f = io .StringIO ("A|B|C\n 1|2|3\n 1|2|3" )
552+ dialect , header = owcsvimport .sniff_csv (f )
553+ self .assertEqual (dialect .delimiter , "|" )
554+ self .assertTrue (header )
555+ with self .assertRaises (csv .Error ):
556+ owcsvimport .sniff_csv (f , delimiters = ["." ])
557+
350558
351559def _open_write (path , mode , encoding = None ):
352560 # pylint: disable=import-outside-toplevel
0 commit comments