From 9e2fa06378803e798f6538d83b7cbe590b463491 Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Mon, 6 Oct 2014 19:52:47 +0100 Subject: [PATCH 01/14] add useful tools for testing --- traitsui/tests/_tools.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/traitsui/tests/_tools.py b/traitsui/tests/_tools.py index a719a1800..1e1274945 100644 --- a/traitsui/tests/_tools.py +++ b/traitsui/tests/_tools.py @@ -23,6 +23,7 @@ from traits.etsconfig.api import ETSConfig import traits.trait_notifiers + # ######### Testing tools @contextmanager @@ -73,6 +74,7 @@ def skip_if_not_backend(test_func, backend_name=''): if not _is_current_backend(backend_name): # preserve original name so that it appears in the report orig_name = test_func.__name__ + def test_func(): raise nose.SkipTest test_func.__name__ = orig_name @@ -110,6 +112,7 @@ def skip_if_null(test_func): if _is_current_backend('null'): # preserve original name so that it appears in the report orig_name = test_func.__name__ + def test_func(): raise nose.SkipTest test_func.__name__ = orig_name @@ -176,6 +179,38 @@ def get_dialog_size(ui_control): return ui_control.size().width(), ui_control.size().height() +@contextmanager +def dispose_ui(ui): + """ A context manager that will dispose the ui on exit. + """ + try: + yield + finally: + if ui is not None: + ui.dispose() + + +def get_traitsui_editor(ui, path): + """ Get an editor from a UI using a '/' separated list of trait names. + + '/' is used to access the editor of a trait in a sub-element of the + view. + """ + + names = path.split('/') + + while True: + name = names.pop(0) + editor = ui.get_editors(name)[0] + + if len(names) > 0: + ui = editor._ui + else: + break + + return editor + + # ######### Debug tools def apply_on_children(func, node, _level=0): From da72cb6bd9d9bd0171e0c48de2867ace908b1e7b Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Mon, 6 Oct 2014 23:16:56 +0100 Subject: [PATCH 02/14] rework despose_ui context manager to work with Wx and Qt --- traitsui/tests/_tools.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/traitsui/tests/_tools.py b/traitsui/tests/_tools.py index 1e1274945..8f0c4db45 100644 --- a/traitsui/tests/_tools.py +++ b/traitsui/tests/_tools.py @@ -180,14 +180,27 @@ def get_dialog_size(ui_control): @contextmanager -def dispose_ui(ui): - """ A context manager that will dispose the ui on exit. +def dispose_ui(function, *args, **kwargs): + """ A context manager that will create a ui and dispose it on exit. + """ + ui = function(*args, **kwargs) try: - yield + yield ui finally: if ui is not None: ui.dispose() + if is_current_backend_qt4(): + from pyface.qt import QtGui + QtGui.QApplication.instance().quit() + elif is_current_backend_wx(): + import wx + for w in wx.GetTopLevelWindows(): + wx.CallAfter(w.Close) + app = wx.GetApp() + wx.CallAfter(app.Exit) + app.MainLoop() + def get_traitsui_editor(ui, path): From 7e7434d36862142180bec0bb966e69d08a7a57a3 Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Mon, 6 Oct 2014 23:20:08 +0100 Subject: [PATCH 03/14] update TupleEditor to support BaseTuples and extra validation (including the new ValidatedTuple) --- traitsui/editors/tuple_editor.py | 186 ++++++++++++++++------------ traitsui/tests/test_tuple_editor.py | 162 +++++++++++++++++++++--- 2 files changed, 254 insertions(+), 94 deletions(-) diff --git a/traitsui/editors/tuple_editor.py b/traitsui/editors/tuple_editor.py index d86ca38e5..cddbbe0ee 100644 --- a/traitsui/editors/tuple_editor.py +++ b/traitsui/editors/tuple_editor.py @@ -16,15 +16,16 @@ """ Defines the tuple editor factory for all traits user interface toolkits. """ -#------------------------------------------------------------------------------- +#------------------------------------------------------------------------------ # Imports: -#------------------------------------------------------------------------------- +#------------------------------------------------------------------------------ from __future__ import absolute_import from traits.trait_base import SequenceTypes, enumerate -from traits.api import Bool, HasTraits, List, Tuple, Unicode, Int, Any, TraitType +from traits.api import ( + Bool, Callable, HasTraits, List, BaseTuple, Unicode, Int, Any, TraitType) # CIRCULAR IMPORT FIXME: Importing from the source rather than traits.ui.api # to avoid circular imports, as this EditorFactory will be part of @@ -39,95 +40,105 @@ from ..editor import Editor -#------------------------------------------------------------------------------- + +#------------------------------------------------------------------------------ # 'ToolkitEditorFactory' class: -#------------------------------------------------------------------------------- +#------------------------------------------------------------------------------ -class ToolkitEditorFactory ( EditorFactory ): +class ToolkitEditorFactory(EditorFactory): """ Editor factory for tuple editors. """ - #--------------------------------------------------------------------------- + #-------------------------------------------------------------------------- # Trait definitions: - #--------------------------------------------------------------------------- + #-------------------------------------------------------------------------- # Trait definitions for each tuple field types = Any # Labels for each of the tuple fields - labels = List( Unicode ) + labels = List(Unicode) # Editors for each of the tuple fields: - editors = List( EditorFactory ) + editors = List(EditorFactory) # Number of tuple fields or rows - cols = Int( 1 ) + cols = Int(1) # Is user input set on every keystroke? This is applied to every field # of the tuple, provided the field does not already have an 'auto_set' # metadata or an editor defined. - auto_set = Bool( True ) + auto_set = Bool(True) # Is user input set when the Enter key is pressed? This is applied to # every field of the tuple, provided the field does not already have an # 'enter_set' metadata or an editor defined. - enter_set = Bool( False ) + enter_set = Bool(False) + + # The validation function to use for the Tuple. This will override the + # validation function used when the editable Trait is a ValidatedTuple. + validation = Callable -#------------------------------------------------------------------------------- + +#------------------------------------------------------------------------------ # 'SimpleEditor' class: -#------------------------------------------------------------------------------- +#------------------------------------------------------------------------------ -class SimpleEditor ( Editor ): +class SimpleEditor(Editor): """ Simple style of editor for tuples. The editor displays an editor for each of the fields in the tuple, based on the type of each field. """ - #--------------------------------------------------------------------------- + #-------------------------------------------------------------------------- # Finishes initializing the editor by creating the underlying toolkit # widget: - #--------------------------------------------------------------------------- + #-------------------------------------------------------------------------- - def init ( self, parent ): + def init(self, parent): """ Finishes initializing the editor by creating the underlying toolkit widget. """ - self._ts = ts = TupleStructure( self ) - self._ui = ui = ts.view.ui( ts, parent, kind = 'subpanel' ).set( - parent = self.ui ) + self._ts = ts = TupleStructure(self) + self._ui = ui = ts.view.ui( + ts, parent, kind='subpanel').set(parent=self.ui) self.control = ui.control self.set_tooltip() - #--------------------------------------------------------------------------- + #-------------------------------------------------------------------------- # Updates the editor when the object trait changes external to the editor: - #--------------------------------------------------------------------------- + #-------------------------------------------------------------------------- - def update_editor ( self ): + def update_editor(self): """ Updates the editor when the object trait changes external to the editor. """ ts = self._ts - for i, value in enumerate( self.value ): - setattr( ts, 'f%d' % i, value ) - #--------------------------------------------------------------------------- + for i, value in enumerate(self.value): + setattr(ts, 'f{0}'.format(i), value) + if ts.validation is not None: + setattr(ts, 'invalid{0}'.format(i), False) + + #-------------------------------------------------------------------------- # Returns the editor's control for indicating error status: - #--------------------------------------------------------------------------- + #-------------------------------------------------------------------------- - def get_error_control ( self ): + def get_error_control(self): """ Returns the editor's control for indicating error status. """ return self._ui.get_error_controls() -#------------------------------------------------------------------------------- + +#------------------------------------------------------------------------------ # 'TupleStructure' class: -#------------------------------------------------------------------------------- +#------------------------------------------------------------------------------ -class TupleStructure ( HasTraits ): +class TupleStructure (HasTraits): """ Creates a view containing items for each field in a tuple. """ - #--------------------------------------------------------------------------- + #-------------------------------------------------------------------------- # Trait definitions: - #--------------------------------------------------------------------------- + #-------------------------------------------------------------------------- # Editor this structure is linked to editor = Any @@ -138,49 +149,60 @@ class TupleStructure ( HasTraits ): # Number of tuple fields fields = Int - #--------------------------------------------------------------------------- + # The validation function to use for the Tuple. + validation = Callable + + #-------------------------------------------------------------------------- # Initializes the object: - #--------------------------------------------------------------------------- + #-------------------------------------------------------------------------- - def __init__ ( self, editor ): + def __init__(self, editor): """ Initializes the object. """ factory = editor.factory - types = factory.types - labels = factory.labels + types = factory.types + labels = factory.labels editors = factory.editors - cols = factory.cols + cols = factory.cols + validation = factory.validation # Save the reference to the editor: self.editor = editor - # Get the tuple we are mirroring: + # Get the tuple we are mirroring. object = editor.value # For each tuple field, add a trait with the appropriate trait # definition and default value: - content = [] - self.fields = len( object ) - len_labels = len( labels ) - len_editors = len( editors ) + content = [] + self.fields = len(object) + len_labels = len(labels) + len_editors = len(editors) + # Get global validation function. + type = editor.value_trait.handler + if hasattr(type, 'validation') and validation is None: + self.validation = validation = type.validation + + # Get field types. if types is None: - type = editor.value_trait.handler - if isinstance( type, Tuple ): + if isinstance(type, BaseTuple): types = type.types - if not isinstance( types, SequenceTypes ): - types = [ types ] + if not isinstance(types, SequenceTypes): + types = [types] - len_types = len( types ) + len_types = len(types) if len_types == 0: - types = [ Any ] + types = [Any] len_types = 1 - for i, value in enumerate( object ): - type = types[ i % len_types ] + for i, value in enumerate(object): + type = types[i % len_types] auto_set = enter_set = None + # XXX: Should the trait auto_set and enter_set value override + # the user option defined in the factory? if isinstance(type, TraitType): auto_set = type.auto_set enter_set = type.enter_set @@ -197,40 +219,52 @@ def __init__ ( self, editor ): if i < len_editors: field_editor = editors[i] - name = 'f%d' % i - self.add_trait( name, type( value, event = 'field', - auto_set = auto_set, - enter_set = enter_set ) ) - item = Item( name = name, label = label, editor = field_editor ) + name = 'f{0}'.format(i) + self.add_trait(name, type( + value, event='field', auto_set=auto_set, enter_set=enter_set)) + if validation is not None: + invalid = 'invalid{0}'.format(i) + self.add_trait(invalid, Bool) + else: + invalid = '' + + item = Item( + name, label=label, editor=field_editor, invalid=invalid) if cols <= 1: - content.append( item ) + content.append(item) else: if (i % cols) == 0: - group = Group( orientation = 'horizontal' ) - content.append( group ) + group = Group(orientation='horizontal') + content.append(group) - group.content.append( item ) + group.content.append(item) - self.view = View( Group( show_labels = (len_labels != 0), *content ) ) + self.view = View(Group(show_labels=(len_labels != 0), *content)) - #--------------------------------------------------------------------------- + #-------------------------------------------------------------------------- # Updates the underlying tuple when any field changes value: - #--------------------------------------------------------------------------- + #-------------------------------------------------------------------------- - def _field_changed ( self, name, old, new ): + def _field_changed(self, name, old, new): """ Updates the underlying tuple when any field changes value. """ - index = int( name[ 1: ] ) + editor = self.editor + value = editor.value + index = int(name[1:]) value = self.editor.value - if new != value[ index ]: - self.editor.value = tuple( - [ getattr( self, 'f%d' % i ) for i in range( self.fields ) ] ) + if new != value[index]: + new_value = tuple( + getattr(self, 'f{0}'.format(i)) for i in range(self.fields)) + if self.validation is not None: + if self.validation(new_value): + editor.value = new_value + for i in range(self.fields): + setattr(self, 'invalid{0}'.format(i), False) + else: + setattr(self, 'invalid{0}'.format(index), True) + else: + editor.value = new_value # Define the TupleEditor class. TupleEditor = ToolkitEditorFactory - -### EOF ####################################################################### - - - diff --git a/traitsui/tests/test_tuple_editor.py b/traitsui/tests/test_tuple_editor.py index 56be426ac..27eff4e34 100644 --- a/traitsui/tests/test_tuple_editor.py +++ b/traitsui/tests/test_tuple_editor.py @@ -18,34 +18,160 @@ import unittest -from traits.api import Float, HasStrictTraits, Str, Tuple +from traits.api import Float, Int, HasStrictTraits, Str, Tuple, ValidatedTuple from traits.testing.api import UnittestTools -from traitsui.api import Item, TupleEditor, View +from traitsui.tests._tools import dispose_ui, get_traitsui_editor class DummyModel(HasStrictTraits): - """ Dummy model with a Tuple trait. - """ - data = Tuple(Float, Float, Str) + value_range = ValidatedTuple( + Int(0), Int(1), validation=lambda x: x[0] < x[1]) - traits_view = View(Item(name='data', editor=TupleEditor())) + data = Tuple(Float, Float, Str) class TestTupleEditor(UnittestTools, unittest.TestCase): + def setUp(self): + from traitsui.api import TupleEditor + self.tuple_editor = TupleEditor + def test_value_update(self): # Regression test for #179 - model = DummyModel() - try: - ui = model.edit_traits() - with self.assertTraitChanges(model, 'data', count=1): - model.data = (3, 4.6, 'nono') - finally: - if ui is not None: - ui.dispose() - - -if __name__ == '__main__': - unittest.run() + + dummy_model = DummyModel() + with dispose_ui(dummy_model.edit_traits): + with self.assertTraitChanges(dummy_model, 'data', count=1): + dummy_model.data = (3, 4.6, 'nono') + + def test_ui_creation(self): + dummy_model = DummyModel() + with dispose_ui(dummy_model.edit_traits) as ui: + editor = get_traitsui_editor(ui, 'value_range') + self.assertIsInstance(editor.factory, self.tuple_editor) + self.assertEqual(editor.value, (0, 1)) + self.assertEqual( + editor._ts.trait_get(['f0', 'f1', 'invalid0', 'invalid1']), + {'f0': 0, 'f1': 1, 'invalid0': False, 'invalid1': False}) + + editor = get_traitsui_editor(ui, 'data') + self.assertIsInstance(editor.factory, self.tuple_editor) + self.assertEqual(editor.value, (0.0, 0.0, '')) + self.assertEqual( + editor._ts.trait_get( + ['f0', 'f1', 'f2', 'invalid0', 'invalid1', 'invalid2']), + {'f0': 0.0, 'f1': 0.0, 'f2': ''}) + + def test_ui_invalid_due_to_failing_custom_validate(self): + dummy_model = DummyModel() + with dispose_ui(dummy_model.edit_traits) as ui: + editor = get_traitsui_editor(ui, 'value_range') + fields_ui = editor._ui + f0_editor = get_traitsui_editor(fields_ui, 'f0') + f1_editor = get_traitsui_editor(fields_ui, 'f1') + + f0_editor.value = 5 # 5 < 1 -> invalid + self.assertTrue(f0_editor.invalid) + self.assertFalse(f1_editor.invalid) + self.assertEqual(editor.value, (0, 1)) + + f0_editor.value = -3 # -3 < 1 -> valid + self.assertFalse(f0_editor.invalid) + self.assertFalse(f1_editor.invalid) + self.assertEqual(editor.value, (-3, 1)) + + f1_editor.value = -4 # -3 < -4 -> invalid + self.assertFalse(f0_editor.invalid) + self.assertTrue(f1_editor.invalid) + self.assertEqual(editor.value, (-3, 1)) + + f1_editor.value = 0 # -3 < 0 -> valid + self.assertFalse(f0_editor.invalid) + self.assertFalse(f1_editor.invalid) + self.assertEqual(editor.value, (-3, 0)) + + def test_when_editor_is_used_with_vertical_layout(self): + + class VSimple(HasStrictTraits): + + value_range = ValidatedTuple( + Int(0), Int(1), cols=1, validation=lambda x: x[0] < x[1]) + + dummy_model = VSimple() + with dispose_ui(dummy_model.edit_traits) as ui: + editor = get_traitsui_editor(ui, 'value_range') + self.assertIsInstance(editor.factory, self.tuple_editor) + self.assertEqual(editor.value, (0, 1)) + self.assertEqual( + editor._ts.trait_get(['f0', 'f1', 'invalid0', 'invalid1']), + {'f0': 0, 'f1': 1, 'invalid0': False, 'invalid1': False}) + + def test_when_editor_is_used_with_horizontal_layout(self): + + class HSimple(HasStrictTraits): + + value_range = ValidatedTuple( + Int(0), Int(1), cols=2, validation=lambda x: x[0] < x[1]) + + dummy_model = HSimple() + with dispose_ui(dummy_model.edit_traits) as ui: + editor = get_traitsui_editor(ui, 'value_range') + self.assertIsInstance(editor.factory, self.tuple_editor) + self.assertEqual(editor.value, (0, 1)) + self.assertEqual( + editor._ts.trait_get(['f0', 'f1', 'invalid0', 'invalid1']), + {'f0': 0, 'f1': 1, 'invalid0': False, 'invalid1': False}) + + def test_when_editor_is_used_in_the_view(self): + from traitsui.api import Item, View + + class SimpleWithView(HasStrictTraits): + + value_range = ValidatedTuple( + Int(0), Int(1), validation=lambda x: x[0] < x[1]) + + view = View( + Item('value_range', editor=self.tuple_editor()) + ) + + dummy_model = SimpleWithView() + with dispose_ui(dummy_model.edit_traits) as ui: + editor = get_traitsui_editor(ui, 'value_range') + self.assertIsInstance(editor.factory, self.tuple_editor) + self.assertEqual(editor.value, (0, 1)) + self.assertEqual( + editor._ts.trait_get(['f0', 'f1', 'invalid0', 'invalid1']), + {'f0': 0, 'f1': 1, 'invalid0': False, 'invalid1': False}) + + fields_ui = editor._ui + f0_editor = get_traitsui_editor(fields_ui, 'f0') + f1_editor = get_traitsui_editor(fields_ui, 'f1') + + f1_editor.value = -4 # 0 < -4 -> invalid + self.assertFalse(f0_editor.invalid) + self.assertTrue(f1_editor.invalid) + self.assertEqual(editor.value, (0, 1)) + + def test_invalid_state_reset_on_model_change(self): + dummy_model = DummyModel() + with dispose_ui(dummy_model.edit_traits) as ui: + editor = get_traitsui_editor(ui, 'value_range') + fields_ui = editor._ui + f0_editor = get_traitsui_editor(fields_ui, 'f0') + f1_editor = get_traitsui_editor(fields_ui, 'f1') + + # given + f1_editor.value = -4 # 0 < -4 -> invalid + self.assertFalse(f0_editor.invalid) + self.assertTrue(f1_editor.invalid) + self.assertEqual(editor.value, (0, 1)) + + # when + dummy_model.value_range = (2, 7) + + # then + self.assertEqual( + editor._ts.trait_get(['f0', 'f1', 'invalid0', 'invalid1']), + {'f0': 2, 'f1': 7, 'invalid0': False, 'invalid1': False}) From 9edf8a9340b095467c86cd731d6264954853dd2a Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Tue, 7 Oct 2014 01:14:08 +0100 Subject: [PATCH 04/14] rework dispose_ui to dispose_ui_after that makes sure the ui will be down after the timeout --- traitsui/tests/_tools.py | 44 ++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/traitsui/tests/_tools.py b/traitsui/tests/_tools.py index 8f0c4db45..9b82330be 100644 --- a/traitsui/tests/_tools.py +++ b/traitsui/tests/_tools.py @@ -15,11 +15,12 @@ from functools import partial from contextlib import contextmanager -import nose - import sys +import threading import traceback +import nose + from traits.etsconfig.api import ETSConfig import traits.trait_notifiers @@ -180,29 +181,56 @@ def get_dialog_size(ui_control): @contextmanager -def dispose_ui(function, *args, **kwargs): +def dispose_ui_after(function, timeout, *args, **kwargs): """ A context manager that will create a ui and dispose it on exit. """ ui = function(*args, **kwargs) + + from pyface.gui import GUI + + timeout_event = threading.Event() + + def on_timeout(timeout_event): + timeout_event.set() + dispose_ui(ui) + + gui = GUI() + gui.invoke_after(timeout * 1000, on_timeout, timeout_event) + try: yield ui finally: - if ui is not None: - ui.dispose() + if timeout_event.is_set(): + message = 'UI was forcibly destroyed after {0} sec' + raise AssertionError(message.format(timeout)) + else: + dispose_ui(ui) + + +def dispose_ui(ui): + """ Dispose the ui, by killing the application object. + + """ + from pyface.gui import GUI + if ui is not None or ui.control is None: + ui.dispose() + gui = GUI() if is_current_backend_qt4(): from pyface.qt import QtGui - QtGui.QApplication.instance().quit() + app = QtGui.QApplication.instance() + gui.invoke_later(app.closeAllWindows) + gui.invoke_after(2, app.quit) + app.exec_() elif is_current_backend_wx(): import wx for w in wx.GetTopLevelWindows(): wx.CallAfter(w.Close) app = wx.GetApp() - wx.CallAfter(app.Exit) + gui.invoke_later(app.Exit) app.MainLoop() - def get_traitsui_editor(ui, path): """ Get an editor from a UI using a '/' separated list of trait names. From 79461d0737e4589b73304fdbf4e5ef0df20f79e3 Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Tue, 7 Oct 2014 01:32:26 +0100 Subject: [PATCH 05/14] augment tests --- traitsui/editors/tuple_editor.py | 14 +-- traitsui/tests/_tools.py | 10 ++ traitsui/tests/test_tuple_editor.py | 136 +++++++++++++++++++++++----- 3 files changed, 129 insertions(+), 31 deletions(-) diff --git a/traitsui/editors/tuple_editor.py b/traitsui/editors/tuple_editor.py index cddbbe0ee..054d1b57b 100644 --- a/traitsui/editors/tuple_editor.py +++ b/traitsui/editors/tuple_editor.py @@ -74,8 +74,9 @@ class ToolkitEditorFactory(EditorFactory): # 'enter_set' metadata or an editor defined. enter_set = Bool(False) - # The validation function to use for the Tuple. This will override the - # validation function used when the editable Trait is a ValidatedTuple. + # The validation function to use for the Tuple. If the edited trait offers + # already a validation function then the value of this trait will be + # ignored. validation = Callable @@ -164,7 +165,6 @@ def __init__(self, editor): labels = factory.labels editors = factory.editors cols = factory.cols - validation = factory.validation # Save the reference to the editor: self.editor = editor @@ -181,8 +181,10 @@ def __init__(self, editor): # Get global validation function. type = editor.value_trait.handler - if hasattr(type, 'validation') and validation is None: - self.validation = validation = type.validation + validation = getattr(type, 'validation', None) + if validation is None: + validation = factory.validation + self.validation = validation # Get field types. if types is None: @@ -201,8 +203,6 @@ def __init__(self, editor): type = types[i % len_types] auto_set = enter_set = None - # XXX: Should the trait auto_set and enter_set value override - # the user option defined in the factory? if isinstance(type, TraitType): auto_set = type.auto_set enter_set = type.enter_set diff --git a/traitsui/tests/_tools.py b/traitsui/tests/_tools.py index 9b82330be..6d849a6e3 100644 --- a/traitsui/tests/_tools.py +++ b/traitsui/tests/_tools.py @@ -179,6 +179,16 @@ def get_dialog_size(ui_control): elif is_current_backend_qt4(): return ui_control.size().width(), ui_control.size().height() +def set_value(editor, value): + """ Set the value on the control managed by the editor. + + """ + if is_current_backend_wx(): + editor.control.SetValue(value) + + elif is_current_backend_qt4(): + editor.control.setValue(value) + @contextmanager def dispose_ui_after(function, timeout, *args, **kwargs): diff --git a/traitsui/tests/test_tuple_editor.py b/traitsui/tests/test_tuple_editor.py index 27eff4e34..81cc6a38b 100644 --- a/traitsui/tests/test_tuple_editor.py +++ b/traitsui/tests/test_tuple_editor.py @@ -21,7 +21,8 @@ from traits.api import Float, Int, HasStrictTraits, Str, Tuple, ValidatedTuple from traits.testing.api import UnittestTools -from traitsui.tests._tools import dispose_ui, get_traitsui_editor +from traitsui.tests._tools import ( + dispose_ui_after, get_traitsui_editor, set_value) class DummyModel(HasStrictTraits): @@ -42,20 +43,13 @@ def test_value_update(self): # Regression test for #179 dummy_model = DummyModel() - with dispose_ui(dummy_model.edit_traits): + with dispose_ui_after(dummy_model.edit_traits, 5): with self.assertTraitChanges(dummy_model, 'data', count=1): dummy_model.data = (3, 4.6, 'nono') def test_ui_creation(self): dummy_model = DummyModel() - with dispose_ui(dummy_model.edit_traits) as ui: - editor = get_traitsui_editor(ui, 'value_range') - self.assertIsInstance(editor.factory, self.tuple_editor) - self.assertEqual(editor.value, (0, 1)) - self.assertEqual( - editor._ts.trait_get(['f0', 'f1', 'invalid0', 'invalid1']), - {'f0': 0, 'f1': 1, 'invalid0': False, 'invalid1': False}) - + with dispose_ui_after(dummy_model.edit_traits, 5) as ui: editor = get_traitsui_editor(ui, 'data') self.assertIsInstance(editor.factory, self.tuple_editor) self.assertEqual(editor.value, (0.0, 0.0, '')) @@ -64,34 +58,67 @@ def test_ui_creation(self): ['f0', 'f1', 'f2', 'invalid0', 'invalid1', 'invalid2']), {'f0': 0.0, 'f1': 0.0, 'f2': ''}) - def test_ui_invalid_due_to_failing_custom_validate(self): + editor = get_traitsui_editor(ui, 'value_range') + self.assertIsInstance(editor.factory, self.tuple_editor) + self.assertEqual(editor.value, (0, 1)) + self.assertEqual( + editor._ts.trait_get(['f0', 'f1', 'invalid0', 'invalid1']), + {'f0': 0, 'f1': 1, 'invalid0': False, 'invalid1': False}) + + def test_ui_invalid_due_to_custom_validation(self): dummy_model = DummyModel() - with dispose_ui(dummy_model.edit_traits) as ui: + with dispose_ui_after(dummy_model.edit_traits, 5) as ui: editor = get_traitsui_editor(ui, 'value_range') fields_ui = editor._ui f0_editor = get_traitsui_editor(fields_ui, 'f0') f1_editor = get_traitsui_editor(fields_ui, 'f1') - f0_editor.value = 5 # 5 < 1 -> invalid + set_value(f0_editor, '5') # 5 < 1 -> invalid self.assertTrue(f0_editor.invalid) self.assertFalse(f1_editor.invalid) self.assertEqual(editor.value, (0, 1)) - f0_editor.value = -3 # -3 < 1 -> valid + set_value(f0_editor, '-3') # -3 < 1 -> valid self.assertFalse(f0_editor.invalid) self.assertFalse(f1_editor.invalid) self.assertEqual(editor.value, (-3, 1)) - f1_editor.value = -4 # -3 < -4 -> invalid + set_value(f1_editor, '-4') # -3 < -4 -> invalid self.assertFalse(f0_editor.invalid) self.assertTrue(f1_editor.invalid) self.assertEqual(editor.value, (-3, 1)) - f1_editor.value = 0 # -3 < 0 -> valid + set_value(f1_editor, '0') # -3 < 0 -> valid self.assertFalse(f0_editor.invalid) self.assertFalse(f1_editor.invalid) self.assertEqual(editor.value, (-3, 0)) + def test_ui_invalid_due_to_field_validation(self): + dummy_model = DummyModel() + with dispose_ui_after(dummy_model.edit_traits, 5) as ui: + editor = get_traitsui_editor(ui, 'data') + fields_ui = editor._ui + f0_editor = get_traitsui_editor(fields_ui, 'f0') + f1_editor = get_traitsui_editor(fields_ui, 'f1') + f2_editor = get_traitsui_editor(fields_ui, 'f2') + + set_value(f1_editor, 'nono') # str -> invalid + self.assertFalse(f0_editor._error) + f1_editor.print_traits() + self.assertTrue(f1_editor._error) + self.assertFalse(f2_editor._error) + self.assertEqual(editor.value, (0.0, 0.0, '')) + + editor = get_traitsui_editor(ui, 'value_range') + fields_ui = editor._ui + f0_editor = get_traitsui_editor(fields_ui, 'f0') + f1_editor = get_traitsui_editor(fields_ui, 'f1') + + set_value(f1_editor, '0.2') # float -> invalid + self.assertTrue(f1_editor._error) + self.assertFalse(f0_editor._error) + self.assertEqual(editor.value, (0, 1)) + def test_when_editor_is_used_with_vertical_layout(self): class VSimple(HasStrictTraits): @@ -100,7 +127,7 @@ class VSimple(HasStrictTraits): Int(0), Int(1), cols=1, validation=lambda x: x[0] < x[1]) dummy_model = VSimple() - with dispose_ui(dummy_model.edit_traits) as ui: + with dispose_ui_after(dummy_model.edit_traits, 5) as ui: editor = get_traitsui_editor(ui, 'value_range') self.assertIsInstance(editor.factory, self.tuple_editor) self.assertEqual(editor.value, (0, 1)) @@ -116,7 +143,7 @@ class HSimple(HasStrictTraits): Int(0), Int(1), cols=2, validation=lambda x: x[0] < x[1]) dummy_model = HSimple() - with dispose_ui(dummy_model.edit_traits) as ui: + with dispose_ui_after(dummy_model.edit_traits, 5) as ui: editor = get_traitsui_editor(ui, 'value_range') self.assertIsInstance(editor.factory, self.tuple_editor) self.assertEqual(editor.value, (0, 1)) @@ -127,17 +154,78 @@ class HSimple(HasStrictTraits): def test_when_editor_is_used_in_the_view(self): from traitsui.api import Item, View + class SimpleWithView(HasStrictTraits): + + value_range = ValidatedTuple( + Int(0), Int(1), validation=lambda x: x[0] < x[1]) + + view = View(Item('value_range', editor=self.tuple_editor())) + + dummy_model = SimpleWithView() + with dispose_ui_after(dummy_model.edit_traits, 5) as ui: + editor = get_traitsui_editor(ui, 'value_range') + self.assertIsInstance(editor.factory, self.tuple_editor) + self.assertEqual(editor.value, (0, 1)) + self.assertEqual( + editor._ts.trait_get(['f0', 'f1', 'invalid0', 'invalid1']), + {'f0': 0, 'f1': 1, 'invalid0': False, 'invalid1': False}) + + fields_ui = editor._ui + f0_editor = get_traitsui_editor(fields_ui, 'f0') + f1_editor = get_traitsui_editor(fields_ui, 'f1') + + set_value(f1_editor, '-4') # 0 < -4 -> invalid + self.assertFalse(f0_editor.invalid) + self.assertTrue(f1_editor.invalid) + self.assertEqual(editor.value, (0, 1)) + + def test_when_validation_method_is_provided_in_the_view(self): + from traitsui.api import Item, View + + class SimpleWithView(HasStrictTraits): + + value_range = Tuple(Int(0), Int(1)) + + view = View( + Item( + 'value_range', + editor=self.tuple_editor( + validation=lambda x: x[0] < x[1]))) + + dummy_model = SimpleWithView() + with dispose_ui_after(dummy_model.edit_traits, 5) as ui: + editor = get_traitsui_editor(ui, 'value_range') + self.assertIsInstance(editor.factory, self.tuple_editor) + self.assertEqual(editor.value, (0, 1)) + self.assertEqual( + editor._ts.trait_get(['f0', 'f1', 'invalid0', 'invalid1']), + {'f0': 0, 'f1': 1, 'invalid0': False, 'invalid1': False}) + + fields_ui = editor._ui + f0_editor = get_traitsui_editor(fields_ui, 'f0') + f1_editor = get_traitsui_editor(fields_ui, 'f1') + + set_value(f1_editor, '-4') # 0 < -4 -> invalid + self.assertFalse(f0_editor.invalid) + self.assertTrue(f1_editor.invalid) + self.assertEqual(editor.value, (0, 1)) + + def test_when_validation_in_trait_validation_overrides_view(self): + from traitsui.api import Item, View + class SimpleWithView(HasStrictTraits): value_range = ValidatedTuple( Int(0), Int(1), validation=lambda x: x[0] < x[1]) view = View( - Item('value_range', editor=self.tuple_editor()) - ) + Item( + 'value_range', + editor=self.tuple_editor( + validation=lambda x: x[0] == x[1]))) dummy_model = SimpleWithView() - with dispose_ui(dummy_model.edit_traits) as ui: + with dispose_ui_after(dummy_model.edit_traits, 5) as ui: editor = get_traitsui_editor(ui, 'value_range') self.assertIsInstance(editor.factory, self.tuple_editor) self.assertEqual(editor.value, (0, 1)) @@ -149,21 +237,21 @@ class SimpleWithView(HasStrictTraits): f0_editor = get_traitsui_editor(fields_ui, 'f0') f1_editor = get_traitsui_editor(fields_ui, 'f1') - f1_editor.value = -4 # 0 < -4 -> invalid + set_value(f1_editor, '-4') # 0 < -4 -> invalid self.assertFalse(f0_editor.invalid) self.assertTrue(f1_editor.invalid) self.assertEqual(editor.value, (0, 1)) def test_invalid_state_reset_on_model_change(self): dummy_model = DummyModel() - with dispose_ui(dummy_model.edit_traits) as ui: + with dispose_ui_after(dummy_model.edit_traits, 5) as ui: editor = get_traitsui_editor(ui, 'value_range') fields_ui = editor._ui f0_editor = get_traitsui_editor(fields_ui, 'f0') f1_editor = get_traitsui_editor(fields_ui, 'f1') # given - f1_editor.value = -4 # 0 < -4 -> invalid + set_value(f1_editor, '-4') # 0 < -4 -> invalid self.assertFalse(f0_editor.invalid) self.assertTrue(f1_editor.invalid) self.assertEqual(editor.value, (0, 1)) From 72d2c8193e088210c6083d2b2c564fde64bb6af7 Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Tue, 7 Oct 2014 03:40:00 +0100 Subject: [PATCH 06/14] update tuple tests move to the editor directory and cleanup --- traitsui/tests/_tools.py | 4 +-- .../tests/{ => editors}/test_tuple_editor.py | 35 ++++++++++--------- 2 files changed, 20 insertions(+), 19 deletions(-) rename traitsui/tests/{ => editors}/test_tuple_editor.py (91%) diff --git a/traitsui/tests/_tools.py b/traitsui/tests/_tools.py index 6d849a6e3..94d7da5a9 100644 --- a/traitsui/tests/_tools.py +++ b/traitsui/tests/_tools.py @@ -187,8 +187,8 @@ def set_value(editor, value): editor.control.SetValue(value) elif is_current_backend_qt4(): - editor.control.setValue(value) - + editor.control.setText(value) + editor.update_object() @contextmanager def dispose_ui_after(function, timeout, *args, **kwargs): diff --git a/traitsui/tests/test_tuple_editor.py b/traitsui/tests/editors/test_tuple_editor.py similarity index 91% rename from traitsui/tests/test_tuple_editor.py rename to traitsui/tests/editors/test_tuple_editor.py index 81cc6a38b..dca7d1ef0 100644 --- a/traitsui/tests/test_tuple_editor.py +++ b/traitsui/tests/editors/test_tuple_editor.py @@ -15,7 +15,6 @@ """ Test cases for the TupleEditor object. """ - import unittest from traits.api import Float, Int, HasStrictTraits, Str, Tuple, ValidatedTuple @@ -24,6 +23,9 @@ from traitsui.tests._tools import ( dispose_ui_after, get_traitsui_editor, set_value) +from traits.etsconfig.api import ETSConfig + +ETSConfig.toolkit = 'qt4' class DummyModel(HasStrictTraits): @@ -74,23 +76,23 @@ def test_ui_invalid_due_to_custom_validation(self): f1_editor = get_traitsui_editor(fields_ui, 'f1') set_value(f0_editor, '5') # 5 < 1 -> invalid - self.assertTrue(f0_editor.invalid) - self.assertFalse(f1_editor.invalid) + self.assertTrue(f0_editor.in_error_state()) + self.assertIsNone(f1_editor.in_error_state()) self.assertEqual(editor.value, (0, 1)) set_value(f0_editor, '-3') # -3 < 1 -> valid - self.assertFalse(f0_editor.invalid) - self.assertFalse(f1_editor.invalid) + self.assertIsNone(f0_editor.in_error_state()) + self.assertIsNone(f1_editor.in_error_state()) self.assertEqual(editor.value, (-3, 1)) set_value(f1_editor, '-4') # -3 < -4 -> invalid - self.assertFalse(f0_editor.invalid) - self.assertTrue(f1_editor.invalid) + self.assertIsNone(f0_editor.in_error_state()) + self.assertTrue(f1_editor.in_error_state()) self.assertEqual(editor.value, (-3, 1)) set_value(f1_editor, '0') # -3 < 0 -> valid - self.assertFalse(f0_editor.invalid) - self.assertFalse(f1_editor.invalid) + self.assertIsNone(f0_editor.in_error_state()) + self.assertIsNone(f1_editor.in_error_state()) self.assertEqual(editor.value, (-3, 0)) def test_ui_invalid_due_to_field_validation(self): @@ -103,10 +105,9 @@ def test_ui_invalid_due_to_field_validation(self): f2_editor = get_traitsui_editor(fields_ui, 'f2') set_value(f1_editor, 'nono') # str -> invalid - self.assertFalse(f0_editor._error) - f1_editor.print_traits() - self.assertTrue(f1_editor._error) - self.assertFalse(f2_editor._error) + self.assertFalse(f0_editor.in_error_state()) + self.assertTrue(f1_editor.in_error_state()) + self.assertFalse(f2_editor.in_error_state()) self.assertEqual(editor.value, (0.0, 0.0, '')) editor = get_traitsui_editor(ui, 'value_range') @@ -115,8 +116,8 @@ def test_ui_invalid_due_to_field_validation(self): f1_editor = get_traitsui_editor(fields_ui, 'f1') set_value(f1_editor, '0.2') # float -> invalid - self.assertTrue(f1_editor._error) - self.assertFalse(f0_editor._error) + self.assertTrue(f1_editor.in_error_state()) + self.assertFalse(f0_editor.in_error_state()) self.assertEqual(editor.value, (0, 1)) def test_when_editor_is_used_with_vertical_layout(self): @@ -252,8 +253,8 @@ def test_invalid_state_reset_on_model_change(self): # given set_value(f1_editor, '-4') # 0 < -4 -> invalid - self.assertFalse(f0_editor.invalid) - self.assertTrue(f1_editor.invalid) + self.assertIsNone(f0_editor.in_error_state()) + self.assertTrue(f1_editor.in_error_state()) self.assertEqual(editor.value, (0, 1)) # when From 8cda1f3ce9be19d69ce62bd600ecdab9b856f2fe Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Tue, 7 Oct 2014 10:39:21 +0100 Subject: [PATCH 07/14] remove debug code --- traitsui/tests/editors/test_tuple_editor.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/traitsui/tests/editors/test_tuple_editor.py b/traitsui/tests/editors/test_tuple_editor.py index dca7d1ef0..40ce1cd55 100644 --- a/traitsui/tests/editors/test_tuple_editor.py +++ b/traitsui/tests/editors/test_tuple_editor.py @@ -23,9 +23,6 @@ from traitsui.tests._tools import ( dispose_ui_after, get_traitsui_editor, set_value) -from traits.etsconfig.api import ETSConfig - -ETSConfig.toolkit = 'qt4' class DummyModel(HasStrictTraits): From 462d5f691801713766818dabfadf0c2bfe0c517e Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Sat, 18 Oct 2014 01:04:46 +0100 Subject: [PATCH 08/14] update TupleEditor to the latest changes in the ValidatedTuple PR in traits --- traitsui/editors/tuple_editor.py | 20 ++++++++++---------- traitsui/qt4/text_editor.py | 1 - traitsui/tests/editors/test_tuple_editor.py | 16 ++++++++-------- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/traitsui/editors/tuple_editor.py b/traitsui/editors/tuple_editor.py index 054d1b57b..8bc46f6c7 100644 --- a/traitsui/editors/tuple_editor.py +++ b/traitsui/editors/tuple_editor.py @@ -77,7 +77,7 @@ class ToolkitEditorFactory(EditorFactory): # The validation function to use for the Tuple. If the edited trait offers # already a validation function then the value of this trait will be # ignored. - validation = Callable + fvalidate = Callable #------------------------------------------------------------------------------ @@ -117,7 +117,7 @@ def update_editor(self): for i, value in enumerate(self.value): setattr(ts, 'f{0}'.format(i), value) - if ts.validation is not None: + if ts.fvalidate is not None: setattr(ts, 'invalid{0}'.format(i), False) #-------------------------------------------------------------------------- @@ -151,7 +151,7 @@ class TupleStructure (HasTraits): fields = Int # The validation function to use for the Tuple. - validation = Callable + fvalidate = Callable #-------------------------------------------------------------------------- # Initializes the object: @@ -181,10 +181,10 @@ def __init__(self, editor): # Get global validation function. type = editor.value_trait.handler - validation = getattr(type, 'validation', None) - if validation is None: - validation = factory.validation - self.validation = validation + fvalidate = getattr(type, 'fvalidate', None) + if fvalidate is None: + fvalidate = factory.fvalidate + self.fvalidate = fvalidate # Get field types. if types is None: @@ -222,7 +222,7 @@ def __init__(self, editor): name = 'f{0}'.format(i) self.add_trait(name, type( value, event='field', auto_set=auto_set, enter_set=enter_set)) - if validation is not None: + if fvalidate is not None: invalid = 'invalid{0}'.format(i) self.add_trait(invalid, Bool) else: @@ -255,8 +255,8 @@ def _field_changed(self, name, old, new): if new != value[index]: new_value = tuple( getattr(self, 'f{0}'.format(i)) for i in range(self.fields)) - if self.validation is not None: - if self.validation(new_value): + if self.fvalidate is not None: + if self.fvalidate(new_value): editor.value = new_value for i in range(self.fields): setattr(self, 'invalid{0}'.format(i), False) diff --git a/traitsui/qt4/text_editor.py b/traitsui/qt4/text_editor.py index 8b5677647..88ee30977 100644 --- a/traitsui/qt4/text_editor.py +++ b/traitsui/qt4/text_editor.py @@ -160,7 +160,6 @@ def _get_user_value ( self ): value = self.control.text() except AttributeError: value = self.control.toPlainText() - value = unicode(value) try: diff --git a/traitsui/tests/editors/test_tuple_editor.py b/traitsui/tests/editors/test_tuple_editor.py index 40ce1cd55..a00ba3ed8 100644 --- a/traitsui/tests/editors/test_tuple_editor.py +++ b/traitsui/tests/editors/test_tuple_editor.py @@ -27,7 +27,7 @@ class DummyModel(HasStrictTraits): value_range = ValidatedTuple( - Int(0), Int(1), validation=lambda x: x[0] < x[1]) + Int(0), Int(1), fvalidate=lambda x: x[0] < x[1]) data = Tuple(Float, Float, Str) @@ -122,7 +122,7 @@ def test_when_editor_is_used_with_vertical_layout(self): class VSimple(HasStrictTraits): value_range = ValidatedTuple( - Int(0), Int(1), cols=1, validation=lambda x: x[0] < x[1]) + Int(0), Int(1), cols=1, fvalidate=lambda x: x[0] < x[1]) dummy_model = VSimple() with dispose_ui_after(dummy_model.edit_traits, 5) as ui: @@ -138,7 +138,7 @@ def test_when_editor_is_used_with_horizontal_layout(self): class HSimple(HasStrictTraits): value_range = ValidatedTuple( - Int(0), Int(1), cols=2, validation=lambda x: x[0] < x[1]) + Int(0), Int(1), cols=2, fvalidate=lambda x: x[0] < x[1]) dummy_model = HSimple() with dispose_ui_after(dummy_model.edit_traits, 5) as ui: @@ -155,7 +155,7 @@ def test_when_editor_is_used_in_the_view(self): class SimpleWithView(HasStrictTraits): value_range = ValidatedTuple( - Int(0), Int(1), validation=lambda x: x[0] < x[1]) + Int(0), Int(1), fvalidate=lambda x: x[0] < x[1]) view = View(Item('value_range', editor=self.tuple_editor())) @@ -188,7 +188,7 @@ class SimpleWithView(HasStrictTraits): Item( 'value_range', editor=self.tuple_editor( - validation=lambda x: x[0] < x[1]))) + fvalidate=lambda x: x[0] < x[1]))) dummy_model = SimpleWithView() with dispose_ui_after(dummy_model.edit_traits, 5) as ui: @@ -208,19 +208,19 @@ class SimpleWithView(HasStrictTraits): self.assertTrue(f1_editor.invalid) self.assertEqual(editor.value, (0, 1)) - def test_when_validation_in_trait_validation_overrides_view(self): + def test_when_validation_in_trait_overrides_view(self): from traitsui.api import Item, View class SimpleWithView(HasStrictTraits): value_range = ValidatedTuple( - Int(0), Int(1), validation=lambda x: x[0] < x[1]) + Int(0), Int(1), fvalidate=lambda x: x[0] < x[1]) view = View( Item( 'value_range', editor=self.tuple_editor( - validation=lambda x: x[0] == x[1]))) + fvalidate=lambda x: x[0] == x[1]))) dummy_model = SimpleWithView() with dispose_ui_after(dummy_model.edit_traits, 5) as ui: From c5c17e01a08379fdc0b37f7db10fd788d7a6fbf7 Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Mon, 6 Jun 2016 20:09:47 +0100 Subject: [PATCH 09/14] no need to advertise --- traitsui/tests/editors/test_tuple_editor.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/traitsui/tests/editors/test_tuple_editor.py b/traitsui/tests/editors/test_tuple_editor.py index 0a14ed2aa..8b9c05ec0 100644 --- a/traitsui/tests/editors/test_tuple_editor.py +++ b/traitsui/tests/editors/test_tuple_editor.py @@ -8,9 +8,6 @@ # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # -# Author: Ioannis Tziakos -# Date: Aug 2014 -# #------------------------------------------------------------------------------ import unittest From ef2a78d0c24f3c76dff56a70c883a903b2f96dec Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Mon, 6 Jun 2016 21:06:11 +0100 Subject: [PATCH 10/14] we already test on normal tuples --- traitsui/tests/editors/test_tuple_editor.py | 42 --------------------- 1 file changed, 42 deletions(-) diff --git a/traitsui/tests/editors/test_tuple_editor.py b/traitsui/tests/editors/test_tuple_editor.py index 8b9c05ec0..7da6c16e3 100644 --- a/traitsui/tests/editors/test_tuple_editor.py +++ b/traitsui/tests/editors/test_tuple_editor.py @@ -26,19 +26,6 @@ class DummyModel(HasStrictTraits): data = Tuple(Float, Float, Str) -class TupleEditor(HasTraits): - """Dialog containing a Tuple of two Int's. - """ - - tup = Tuple(Int,Int) - - traits_view = View( - Item(label="Enter 4 and 6, then press OK"), - Item('tup'), - buttons = ['OK'] - ) - - class TestTupleEditor(UnittestTools, unittest.TestCase): def setUp(self): @@ -267,32 +254,3 @@ def test_invalid_state_reset_on_model_change(self): self.assertEqual( editor._ts.trait_get(['f0', 'f1', 'invalid0', 'invalid1']), {'f0': 2, 'f1': 7, 'invalid0': False, 'invalid1': False}) - - @skip_if_not_qt4 - def test_qt_tuple_editor(): - # Behavior: when editing the text of a tuple editor, - # value get updated immediately. - - from pyface import qt - - with store_exceptions_on_all_threads(): - val = TupleEditor() - ui = val.edit_traits() - - # the following is equivalent to clicking in the text control of the - # range editor, enter a number, and clicking ok without defocusing - - # text element inside the spin control - lineedits = ui.control.findChildren(qt.QtGui.QLineEdit) - lineedits[0].setFocus() - lineedits[0].clear() - lineedits[0].insert('4') - lineedits[1].setFocus() - lineedits[1].clear() - lineedits[1].insert('6') - - # if all went well, the tuple trait has been updated and its value is 4 - assert val.tup == (4,6) - - # press the OK button and close the dialog - press_ok_button(ui) From 4edd88c742fa320c5213cc6e2326e554e7f18bd3 Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Mon, 6 Jun 2016 21:13:45 +0100 Subject: [PATCH 11/14] add support for running the tests as a standalone test --- traitsui/tests/editors/test_tuple_editor.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/traitsui/tests/editors/test_tuple_editor.py b/traitsui/tests/editors/test_tuple_editor.py index 7da6c16e3..08182d051 100644 --- a/traitsui/tests/editors/test_tuple_editor.py +++ b/traitsui/tests/editors/test_tuple_editor.py @@ -254,3 +254,7 @@ def test_invalid_state_reset_on_model_change(self): self.assertEqual( editor._ts.trait_get(['f0', 'f1', 'invalid0', 'invalid1']), {'f0': 2, 'f1': 7, 'invalid0': False, 'invalid1': False}) + + +if __name__ == '__main__': + unittest.main() From 510b16e710069098a8c9128bf21d894e891cc48d Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Mon, 6 Jun 2016 21:15:22 +0100 Subject: [PATCH 12/14] skip on null toolkit --- traitsui/tests/editors/test_tuple_editor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/traitsui/tests/editors/test_tuple_editor.py b/traitsui/tests/editors/test_tuple_editor.py index 08182d051..a66298408 100644 --- a/traitsui/tests/editors/test_tuple_editor.py +++ b/traitsui/tests/editors/test_tuple_editor.py @@ -15,7 +15,7 @@ from traits.testing.api import UnittestTools from traitsui.tests._tools import ( - dispose_ui_after, get_traitsui_editor, set_value) + dispose_ui_after, get_traitsui_editor, set_value, skip_if_null) class DummyModel(HasStrictTraits): @@ -28,6 +28,7 @@ class DummyModel(HasStrictTraits): class TestTupleEditor(UnittestTools, unittest.TestCase): + @skip_if_null def setUp(self): from traitsui.api import TupleEditor self.tuple_editor = TupleEditor From fb090398269d6fb99d17035dead5642ca1f43c7d Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Mon, 6 Jun 2016 21:25:36 +0100 Subject: [PATCH 13/14] better support for skip on null toolkit --- traitsui/tests/_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/traitsui/tests/_tools.py b/traitsui/tests/_tools.py index 94d7da5a9..3c674111b 100644 --- a/traitsui/tests/_tools.py +++ b/traitsui/tests/_tools.py @@ -114,7 +114,7 @@ def skip_if_null(test_func): # preserve original name so that it appears in the report orig_name = test_func.__name__ - def test_func(): + def test_func(self=None): raise nose.SkipTest test_func.__name__ = orig_name From 611a382ddb6158e3d7764a333ac24f09a488aae4 Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Tue, 11 Oct 2016 22:40:29 +0100 Subject: [PATCH 14/14] all fields turn invalid when validation fails --- traitsui/editors/tuple_editor.py | 3 ++- traitsui/tests/editors/test_tuple_editor.py | 15 +++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/traitsui/editors/tuple_editor.py b/traitsui/editors/tuple_editor.py index edd47dd1f..e8562685b 100644 --- a/traitsui/editors/tuple_editor.py +++ b/traitsui/editors/tuple_editor.py @@ -214,7 +214,8 @@ def _field_changed(self, name, old, new): for i in range(self.fields): setattr(self, 'invalid{0}'.format(i), False) else: - setattr(self, 'invalid{0}'.format(index), True) + for i in range(self.fields): + setattr(self, 'invalid{0}'.format(i), True) else: editor.value = new_value diff --git a/traitsui/tests/editors/test_tuple_editor.py b/traitsui/tests/editors/test_tuple_editor.py index a1779ed54..1f5508d32 100644 --- a/traitsui/tests/editors/test_tuple_editor.py +++ b/traitsui/tests/editors/test_tuple_editor.py @@ -37,6 +37,9 @@ def setUp(self): from traitsui.api import TupleEditor self.tuple_editor = TupleEditor + def tearDown(self): + self.tuple_editor = None + def test_value_update(self): # Regression test for #179 dummy_model = DummyModel() @@ -72,7 +75,7 @@ def test_ui_invalid_due_to_custom_validation(self): set_value(f0_editor, '5') # 5 < 1 -> invalid self.assertTrue(f0_editor.in_error_state()) - self.assertIsNone(f1_editor.in_error_state()) + self.assertTrue(f1_editor.in_error_state()) self.assertEqual(editor.value, (0, 1)) set_value(f0_editor, '-3') # -3 < 1 -> valid @@ -81,7 +84,7 @@ def test_ui_invalid_due_to_custom_validation(self): self.assertEqual(editor.value, (-3, 1)) set_value(f1_editor, '-4') # -3 < -4 -> invalid - self.assertIsNone(f0_editor.in_error_state()) + self.assertTrue(f0_editor.in_error_state()) self.assertTrue(f1_editor.in_error_state()) self.assertEqual(editor.value, (-3, 1)) @@ -170,7 +173,7 @@ class SimpleWithView(HasStrictTraits): f1_editor = get_traitsui_editor(fields_ui, 'f1') set_value(f1_editor, '-4') # 0 < -4 -> invalid - self.assertFalse(f0_editor.invalid) + self.assertTrue(f0_editor.invalid) self.assertTrue(f1_editor.invalid) self.assertEqual(editor.value, (0, 1)) @@ -201,7 +204,7 @@ class SimpleWithView(HasStrictTraits): f1_editor = get_traitsui_editor(fields_ui, 'f1') set_value(f1_editor, '-4') # 0 < -4 -> invalid - self.assertFalse(f0_editor.invalid) + self.assertTrue(f0_editor.invalid) self.assertTrue(f1_editor.invalid) self.assertEqual(editor.value, (0, 1)) @@ -233,7 +236,7 @@ class SimpleWithView(HasStrictTraits): f1_editor = get_traitsui_editor(fields_ui, 'f1') set_value(f1_editor, '-4') # 0 < -4 -> invalid - self.assertFalse(f0_editor.invalid) + self.assertTrue(f0_editor.invalid) self.assertTrue(f1_editor.invalid) self.assertEqual(editor.value, (0, 1)) @@ -247,7 +250,7 @@ def test_invalid_state_reset_on_model_change(self): # given set_value(f1_editor, '-4') # 0 < -4 -> invalid - self.assertIsNone(f0_editor.in_error_state()) + self.assertTrue(f0_editor.in_error_state()) self.assertTrue(f1_editor.in_error_state()) self.assertEqual(editor.value, (0, 1))