From d31f34a7d12503fbc27a58ea1e60e68661983c68 Mon Sep 17 00:00:00 2001 From: Corran Webster Date: Mon, 6 Sep 2021 13:35:28 +0100 Subject: [PATCH 1/4] Add tests for Group.get_shadow. --- traitsui/tests/test_group.py | 280 +++++++++++++++++++++++++++++++++++ 1 file changed, 280 insertions(+) create mode 100644 traitsui/tests/test_group.py diff --git a/traitsui/tests/test_group.py b/traitsui/tests/test_group.py new file mode 100644 index 000000000..f42dfdefe --- /dev/null +++ b/traitsui/tests/test_group.py @@ -0,0 +1,280 @@ +# (C) Copyright 2004-2021 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + +""" +Test cases for the UI object. +""" + +import contextlib +import unittest +import unittest.mock + +from pyface.api import GUI +from traits.api import Property +from traits.has_traits import HasTraits, HasStrictTraits +from traits.trait_types import Str, Int + +from traitsui.basic_editor_factory import BasicEditorFactory +from traitsui.api import Group, Include, Item, spring, View +from traitsui.group import ShadowGroup +from traitsui.tests._tools import ( + BaseTestMixin, + count_calls, + create_ui, + is_qt, + is_wx, + process_cascade_events, + requires_toolkit, + reraise_exceptions, + ToolkitName, +) +from traitsui.toolkit import toolkit, toolkit_object + + +class BaseWithInclude(HasTraits): + + x = Str() + + traits_view = View( + Include('included_group'), + ) + +class SubclassWithInclude(BaseWithInclude): + + included_group = Group('x') + + +class TestGroup(BaseTestMixin, unittest.TestCase): + + def test_get_shadow_item(self): + """ + Given a group with an item + When get_shadow is called + Then it returns a shadow group with the same item + """ + item = Item('x') + group = Group(item) + ui = unittest.mock.Mock() + + result = group.get_shadow(ui) + + self.assertIsInstance(result, ShadowGroup) + self.assertIs(result.shadow, group) + self.assertEqual(len(result.content), 1) + self.assertIs(result.content[0], item) + self.assertEqual(result.groups, 0) + ui.find.assert_not_called() + + def test_get_shadow_item_defined_when_true(self): + """ + Given a group with an item that has defined_when evaluate to True + When get_shadow is called + Then it returns a shadow group with the same item + """ + item = Item('x', defined_when="True") + group = Group(item) + ui = unittest.mock.Mock(**{'eval_when.return_value': True}) + + result = group.get_shadow(ui) + + self.assertIsInstance(result, ShadowGroup) + self.assertIs(result.shadow, group) + self.assertEqual(len(result.content), 1) + self.assertIs(result.content[0], item) + self.assertEqual(result.groups, 0) + ui.find.assert_not_called() + ui.eval_when.assert_called_once() + + def test_get_shadow_item_defined_when_false(self): + """ + Given a group with an item that has defined_when evaluate to False + When get_shadow is called + Then it returns a shadow group with no items + """ + item = Item('x', defined_when="False") + group = Group(item) + ui = unittest.mock.Mock(**{'eval_when.return_value': False}) + + result = group.get_shadow(ui) + + self.assertIsInstance(result, ShadowGroup) + self.assertIs(result.shadow, group) + self.assertEqual(len(result.content), 0) + self.assertEqual(result.groups, 0) + ui.find.assert_not_called() + ui.eval_when.assert_called_once() + + def test_get_shadow_sub_group(self): + """ + Given a group with a sub-group + When get_shadow is called + Then it returns a shadow group with a shadow group for the sub-group + """ + sub_group = Group(Item('x')) + group = Group(sub_group) + ui = unittest.mock.Mock() + + result = group.get_shadow(ui) + + self.assertIsInstance(result, ShadowGroup) + self.assertIs(result.shadow, group) + self.assertEqual(len(result.content), 1) + shadow_subgroup = result.content[0] + self.assertIsInstance(shadow_subgroup, ShadowGroup) + self.assertIs(shadow_subgroup.shadow, sub_group) + self.assertEqual(result.groups, 1) + ui.find.assert_not_called() + + def test_get_shadow_sub_group_defined_when_true(self): + """ + Given a group with a sub-group that has defined_when evaluate to True + When get_shadow is called + Then it returns a shadow group with a shadow group for the sub-group + """ + sub_group = Group(Item('x'), defined_when="True") + group = Group(sub_group) + ui = unittest.mock.Mock(**{'eval_when.return_value': True}) + + result = group.get_shadow(ui) + + self.assertIsInstance(result, ShadowGroup) + self.assertIs(result.shadow, group) + self.assertEqual(len(result.content), 1) + shadow_subgroup = result.content[0] + self.assertIsInstance(shadow_subgroup, ShadowGroup) + self.assertIs(shadow_subgroup.shadow, sub_group) + self.assertEqual(result.groups, 1) + ui.find.assert_not_called() + ui.eval_when.assert_called_once() + + def test_get_shadow_sub_group_defined_when_false(self): + """ + Given a group with a sub-group that has defined_when evaluate to False + When get_shadow is called + Then it returns a shadow group with a shadow group for the sub-group + """ + sub_group = Group(Item('x'), defined_when="False") + group = Group(sub_group) + ui = unittest.mock.Mock(**{'eval_when.return_value': False}) + + result = group.get_shadow(ui) + + self.assertIsInstance(result, ShadowGroup) + self.assertIs(result.shadow, group) + self.assertEqual(len(result.content), 0) + self.assertEqual(result.groups, 0) + ui.find.assert_not_called() + ui.eval_when.assert_called_once() + + def test_get_shadow_include_none(self): + """ + Given a group with an include and the include resolves to None + When get_shadow is called + Then it returns a shadow group with no content + """ + group = Group(Include('test_include')) + ui = unittest.mock.Mock(**{'find.return_value': None}) + + result = group.get_shadow(ui) + + self.assertIsInstance(result, ShadowGroup) + self.assertIs(result.shadow, group) + self.assertEqual(len(result.content), 0) + self.assertEqual(result.groups, 0) + ui.find.assert_called_once() + + def test_get_shadow_include_item(self): + """ + Given a group with an include and the include resolves to an item + When get_shadow is called + Then it returns a shadow group with the same item + """ + include_group = Group(Include('test_include')) + item = Item('x') + ui = unittest.mock.Mock(**{'find.return_value': item}) + + result = include_group.get_shadow(ui) + + self.assertIsInstance(result, ShadowGroup) + self.assertIs(result.shadow, include_group) + self.assertEqual(len(result.content), 1) + self.assertIs(result.content[0], item) + self.assertEqual(result.groups, 0) + ui.find.assert_called_once() + + def test_get_shadow_include_sub_group(self): + """ + Given a group with an include and the include resolves to a group + When get_shadow is called + Then it returns a shadow group with a shadow group for the sub-group + """ + sub_group = Group(Item('x')) + group = Group(Include('test_include')) + ui = unittest.mock.Mock(**{'find.return_value': sub_group}) + + result = group.get_shadow(ui) + + self.assertIsInstance(result, ShadowGroup) + self.assertIs(result.shadow, group) + self.assertEqual(len(result.content), 1) + shadow_subgroup = result.content[0] + self.assertIsInstance(shadow_subgroup, ShadowGroup) + self.assertIs(shadow_subgroup.shadow, sub_group) + self.assertEqual(result.groups, 1) + ui.find.assert_called_once() + + def test_get_shadow_sub_group_defined_when_true(self): + """ + Given a group with an include and the include resolves to a group + that has defined_when evaluate to True + When get_shadow is called + Then it returns a shadow group with a shadow group for the sub-group + """ + sub_group = Group(Item('x'), defined_when="True") + group = Group(Include('test_include')) + ui = unittest.mock.Mock(**{ + 'find.return_value': sub_group, + 'eval_when.return_value': True, + }) + + result = group.get_shadow(ui) + + self.assertIsInstance(result, ShadowGroup) + self.assertIs(result.shadow, group) + self.assertEqual(len(result.content), 1) + shadow_subgroup = result.content[0] + self.assertIsInstance(shadow_subgroup, ShadowGroup) + self.assertIs(shadow_subgroup.shadow, sub_group) + self.assertEqual(result.groups, 1) + ui.find.assert_called_once() + ui.eval_when.assert_called_once() + + def test_get_shadow_sub_group_defined_when_false(self): + """ + Given a group with an include and the include resolves to a group + that has defined_when evaluate to True + When get_shadow is called + Then it returns a shadow group with a shadow group for the sub-group + """ + sub_group = Group(Item('x'), defined_when="False") + group = Group(Include('test_include')) + ui = unittest.mock.Mock(**{ + 'find.return_value': sub_group, + 'eval_when.return_value': False, + }) + + result = group.get_shadow(ui) + + self.assertIsInstance(result, ShadowGroup) + self.assertIs(result.shadow, group) + self.assertEqual(len(result.content), 0) + self.assertEqual(result.groups, 0) + ui.find.assert_called_once() + ui.eval_when.assert_called_once() From 31c790c869fa126aeeff820942f9302500d0da80 Mon Sep 17 00:00:00 2001 From: Corran Webster Date: Mon, 6 Sep 2021 14:48:24 +0100 Subject: [PATCH 2/4] Add tests for ShadowGroup.get_content. --- traitsui/tests/test_group.py | 164 +++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) diff --git a/traitsui/tests/test_group.py b/traitsui/tests/test_group.py index f42dfdefe..bb855f4c7 100644 --- a/traitsui/tests/test_group.py +++ b/traitsui/tests/test_group.py @@ -278,3 +278,167 @@ def test_get_shadow_sub_group_defined_when_false(self): self.assertEqual(result.groups, 0) ui.find.assert_called_once() ui.eval_when.assert_called_once() + + +class TestShadowGroup(BaseTestMixin, unittest.TestCase): + + def test_get_content_all_items(self): + """ + Given a ShadowGroup with only Items + When get_content is called + Then it returns the list of Items + """ + item_x = Item('x') + item_y = Item('y') + group = Group(item_x, item_y) + shadow_group = ShadowGroup( + shadow=group, + content=group.content, + groups=0, + ) + + result = shadow_group.get_content() + + self.assertEqual(len(result), 2) + self.assertIs(result[0], item_x) + self.assertIs(result[1], item_y) + + def test_get_content_all_subgroups_allow_groups(self): + """ + Given a ShadowGroup with only Groups + When get_content is called with allow_groups + Then it returns the list of Groups + """ + item_x = Item('x') + group_x = Group(item_x) + shadow_group_x = ShadowGroup( + shadow=group_x, + content=group_x.content, + groups=0, + ) + item_y = Item('y') + group_y = Group(item_y) + shadow_group_y = ShadowGroup( + shadow=group_y, + content=group_y.content, + groups=0, + ) + group = Group(group_x, group_y) + shadow_group = ShadowGroup( + shadow=group, + content=[shadow_group_x, shadow_group_y], + groups=2, + ) + + result = shadow_group.get_content() + + self.assertEqual(len(result), 2) + self.assertIs(result[0], shadow_group_x) + self.assertIs(result[1], shadow_group_y) + + def test_get_content_mixed_allow_groups(self): + """ + Given a ShadowGroup with only Groups + When get_content is called with allow_groups + Then it assembles runs of items into groups + """ + item_x = Item('x') + group_x = Group(item_x) + shadow_group_x = ShadowGroup( + shadow=group_x, + content=group_x.content, + groups=0, + ) + item_y = Item('y') + group_y = Group(item_y) + shadow_group_y = ShadowGroup( + shadow=group_y, + content=group_y.content, + groups=0, + ) + item_z = Item('z') + group = Group(group_x, item_z, group_y) + shadow_group = ShadowGroup( + shadow=group, + content=[shadow_group_x, item_z, shadow_group_y], + groups=2, + ) + + result = shadow_group.get_content() + + self.assertEqual(len(result), 3) + self.assertIs(result[0], shadow_group_x) + self.assertIsInstance(result[1], ShadowGroup) + shadow_group_z = result[1] + self.assertIs(shadow_group_z.shadow, group) + self.assertEqual(len(shadow_group_z.content), 1) + self.assertIs(shadow_group_z.content[0], item_z) + self.assertIs(result[2], shadow_group_y) + + def test_get_content_all_subgroups_allow_groups_false(self): + """ + Given a ShadowGroup with only Groups + When get_content is called with allow_groups False + Then it returns the flattened list of items. + """ + item_x = Item('x') + group_x = Group(item_x) + shadow_group_x = ShadowGroup( + shadow=group_x, + content=group_x.content, + groups=0, + ) + item_y = Item('y') + group_y = Group(item_y) + shadow_group_y = ShadowGroup( + shadow=group_y, + content=group_y.content, + groups=0, + ) + group = Group(group_x, group_y) + shadow_group = ShadowGroup( + shadow=group, + content=[shadow_group_x, shadow_group_y], + groups=2, + ) + + result = shadow_group.get_content(False) + + self.assertEqual(len(result), 2) + self.assertIs(result[0], item_x) + self.assertIs(result[1], item_y) + + def test_get_content_mixed_allow_groups(self): + """ + Given a ShadowGroup with only Groups + When get_content is called with allow_groups + Then it returns the flattened list of items. + """ + item_x = Item('x') + group_x = Group(item_x) + shadow_group_x = ShadowGroup( + shadow=group_x, + content=group_x.content, + groups=0, + ) + item_y = Item('y') + group_y = Group(item_y) + shadow_group_y = ShadowGroup( + shadow=group_y, + content=group_y.content, + groups=0, + ) + item_z = Item('z') + group = Group(group_x, item_z, group_y) + shadow_group = ShadowGroup( + shadow=group, + content=[shadow_group_x, item_z, shadow_group_y], + groups=2, + ) + + result = shadow_group.get_content(False) + + self.assertEqual(len(result), 3) + self.assertIs(result[0], item_x) + self.assertIs(result[1], item_z) + self.assertIs(result[2], item_y) From 3c75538f3d1711d0eafd46a881fa28a8431dbe7e Mon Sep 17 00:00:00 2001 From: Corran Webster Date: Mon, 6 Sep 2021 16:48:00 +0100 Subject: [PATCH 3/4] Only create shadow groups when we need to. Otherwise the original Group can stand-in for itself. --- traitsui/group.py | 149 +++++++++++-------- traitsui/tests/test_group.py | 269 +++++++++++++++++++++++++---------- 2 files changed, 289 insertions(+), 129 deletions(-) diff --git a/traitsui/group.py b/traitsui/group.py index 250e9fd59..5a1541328 100644 --- a/traitsui/group.py +++ b/traitsui/group.py @@ -19,6 +19,7 @@ Delegate, Float, Instance, + Int, List, Property, Range, @@ -195,6 +196,9 @@ class Group(ViewSubElement): #: Requested height of the group (calculated from heights of contents) height = Property(Float, observe="content") + #: The number of sub-groups in the contents. + groups = Property(Int, observe="content.items") + def __init__(self, *values, **traits): """ Initializes the group object. """ @@ -271,34 +275,86 @@ def replace_include(self, view_elements): view_elements.content[id] = item item.replace_include(view_elements) + def get_content(self, allow_groups=True): + """ Returns the contents of the Group within a specified context for + building a user interface. + + This method assumes that there are no Include or False defined_when + clauses in the contetns of the group: if that is the case the group + should have been replaced by a ShadowGroup with the appropriate + content substituted, and the ShadowGroup's method will be called + instead. + """ + # Make a copy of the content: + result = self.content[:] + + # If result includes any Groups and they are not allowed, + # replace them: + if self.groups > 0: + if not allow_groups: + i = 0 + while i < len(result): + value = result[i] + if isinstance(value, Group): + items = value.get_content(False) + result[i : i + 1] = items + i += len(items) + else: + i += 1 + elif (self.groups != len(result)) and (self.layout == "normal"): + # if we have a mix of groups and items, create groups around runs of + # items + items = [] + content = [] + for item in result: + if isinstance(item, Group): + self._flush_items(content, items) + content.append(item) + else: + items.append(item) + self._flush_items(content, items) + result = content + + # Return the resulting list of objects: + return result + def get_shadow(self, ui): """ Returns a ShadowGroup object for the current Group object, which recursively resolves all embedded Include objects and which replaces each embedded Group object with a corresponding ShadowGroup. """ content = [] - groups = 0 level = ui.push_level() + shadow_needed = False for value in self.content: # Recursively replace Include objects: while isinstance(value, Include): value = ui.find(value) + shadow_needed = True # Convert Group objects to ShadowGroup objects, but include Item # objects as is (ignore any 'None' values caused by a failed # Include): if isinstance(value, Group): if self._defined_when(ui, value): - content.append(value.get_shadow(ui)) - groups += 1 + shadow_value = value.get_shadow(ui) + content.append(shadow_value) + shadow_needed |= isinstance(shadow_value, ShadowGroup) + else: + shadow_needed = shadow_needed or (value.defined_when != "") elif isinstance(value, Item): if self._defined_when(ui, value): content.append(value) + else: + shadow_needed = shadow_needed or (value.defined_when != "") ui.pop_level(level) - # Return the ShadowGroup: - return ShadowGroup(shadow=self, content=content, groups=groups) + if shadow_needed: + # Return the ShadowGroup: + return ShadowGroup(shadow=self, content=content) + else: + return self def set_container(self): """ Sets the correct container for the content. @@ -306,6 +362,14 @@ def set_container(self): for item in self.content: item.container = self + def get_id(self): + """ Returns an ID for the group. + """ + if self.id != "": + return self.id + + return ":".join([item.get_id() for item in self.get_content()]) + def _defined_when(self, ui, value): """ Should the object be defined in the user interface? """ @@ -345,6 +409,24 @@ def _parsed_label(self): """ self.show_border = True + def _flush_items(self, content, items): + """ Creates a shadow sub-group for any items contained in a specified list. + """ + if len(items) > 0: + content.append( + # Set shadow before hand to prevent delegation errors + ShadowGroup(shadow=self).trait_set( + label="", + show_border=False, + content=items, + show_labels=self.show_labels, + show_left=self.show_left, + springy=self.springy, + orientation=self.orientation, + ) + ) + del items[:] + def __repr__(self): """ Returns a "pretty print" version of the Group. """ @@ -413,6 +495,10 @@ def _get_height(self): return height + @cached_property + def _get_groups(self): + return len([item for item in self.content if isinstance(item, Group)]) + class HGroup(Group): """ A group whose items are laid out horizontally. @@ -557,9 +643,6 @@ def __init__(self, shadow, **traits): #: Group object this is a "shadow" for shadow = ReadOnly() - #: Number of ShadowGroups in **content** - groups = ReadOnly() - #: Name of the group id = ShadowDelegate @@ -630,66 +713,18 @@ def __init__(self, shadow, **traits): #: Style sheet for the panel style_sheet = ShadowDelegate - def get_content(self, allow_groups=True): - """ Returns the contents of the Group within a specified context for - building a user interface. - - This method makes sure that all Group types are of the same type (i.e., - Group or Item) and that all Include objects have been replaced by their - substituted values. - """ - # Make a copy of the content: - result = self.content[:] - - # If result includes any ShadowGroups and they are not allowed, - # replace them: - if self.groups != 0: - if not allow_groups: - i = 0 - while i < len(result): - value = result[i] - if isinstance(value, ShadowGroup): - items = value.get_content(False) - result[i : i + 1] = items - i += len(items) - else: - i += 1 - elif (self.groups != len(result)) and (self.layout == "normal"): - items = [] - content = [] - for item in result: - if isinstance(item, ShadowGroup): - self._flush_items(content, items) - content.append(item) - else: - items.append(item) - self._flush_items(content, items) - result = content - - # Return the resulting list of objects: - return result - - def get_id(self): - """ Returns an ID for the group. - """ - if self.id != "": - return self.id - - return ":".join([item.get_id() for item in self.get_content()]) - def set_container(self): """ Sets the correct container for the content. """ pass def _flush_items(self, content, items): - """ Creates a sub-group for any items contained in a specified list. + """ Creates a shadow sub-group for any items contained in a specified list. """ if len(items) > 0: content.append( # Set shadow before hand to prevent delegation errors ShadowGroup(shadow=self.shadow).trait_set( - groups=0, label="", show_border=False, content=items, diff --git a/traitsui/tests/test_group.py b/traitsui/tests/test_group.py index bb855f4c7..4f7d27482 100644 --- a/traitsui/tests/test_group.py +++ b/traitsui/tests/test_group.py @@ -13,6 +13,7 @@ """ import contextlib +from re import sub import unittest import unittest.mock @@ -57,7 +58,7 @@ def test_get_shadow_item(self): """ Given a group with an item When get_shadow is called - Then it returns a shadow group with the same item + Then it returns the group """ item = Item('x') group = Group(item) @@ -65,18 +66,14 @@ def test_get_shadow_item(self): result = group.get_shadow(ui) - self.assertIsInstance(result, ShadowGroup) - self.assertIs(result.shadow, group) - self.assertEqual(len(result.content), 1) - self.assertIs(result.content[0], item) - self.assertEqual(result.groups, 0) + self.assertIs(result, group) ui.find.assert_not_called() def test_get_shadow_item_defined_when_true(self): """ Given a group with an item that has defined_when evaluate to True When get_shadow is called - Then it returns a shadow group with the same item + Then it returns the group """ item = Item('x', defined_when="True") group = Group(item) @@ -84,11 +81,7 @@ def test_get_shadow_item_defined_when_true(self): result = group.get_shadow(ui) - self.assertIsInstance(result, ShadowGroup) - self.assertIs(result.shadow, group) - self.assertEqual(len(result.content), 1) - self.assertIs(result.content[0], item) - self.assertEqual(result.groups, 0) + self.assertIs(result, group) ui.find.assert_not_called() ui.eval_when.assert_called_once() @@ -115,7 +108,7 @@ def test_get_shadow_sub_group(self): """ Given a group with a sub-group When get_shadow is called - Then it returns a shadow group with a shadow group for the sub-group + Then it returns the group """ sub_group = Group(Item('x')) group = Group(sub_group) @@ -123,6 +116,22 @@ def test_get_shadow_sub_group(self): result = group.get_shadow(ui) + self.assertIs(result, group) + ui.find.assert_not_called() + + def test_get_shadow_sub_group_recurses(self): + """ + Given a group with a sub-group + which returns a ShadowGroup from get_shadow + When get_shadow is called + Then it returns a shadow group with a shadow group for the subgroup + """ + sub_group = Group(Item('x', defined_when="False")) + group = Group(sub_group) + ui = unittest.mock.Mock(**{'eval_when.return_value': False}) + + result = group.get_shadow(ui) + self.assertIsInstance(result, ShadowGroup) self.assertIs(result.shadow, group) self.assertEqual(len(result.content), 1) @@ -136,7 +145,7 @@ def test_get_shadow_sub_group_defined_when_true(self): """ Given a group with a sub-group that has defined_when evaluate to True When get_shadow is called - Then it returns a shadow group with a shadow group for the sub-group + Then it returns the group. """ sub_group = Group(Item('x'), defined_when="True") group = Group(sub_group) @@ -144,13 +153,7 @@ def test_get_shadow_sub_group_defined_when_true(self): result = group.get_shadow(ui) - self.assertIsInstance(result, ShadowGroup) - self.assertIs(result.shadow, group) - self.assertEqual(len(result.content), 1) - shadow_subgroup = result.content[0] - self.assertIsInstance(shadow_subgroup, ShadowGroup) - self.assertIs(shadow_subgroup.shadow, sub_group) - self.assertEqual(result.groups, 1) + self.assertIs(result, group) ui.find.assert_not_called() ui.eval_when.assert_called_once() @@ -213,7 +216,7 @@ def test_get_shadow_include_sub_group(self): """ Given a group with an include and the include resolves to a group When get_shadow is called - Then it returns a shadow group with a shadow group for the sub-group + Then it returns a shadow group containing the subgroup """ sub_group = Group(Item('x')) group = Group(Include('test_include')) @@ -224,9 +227,7 @@ def test_get_shadow_include_sub_group(self): self.assertIsInstance(result, ShadowGroup) self.assertIs(result.shadow, group) self.assertEqual(len(result.content), 1) - shadow_subgroup = result.content[0] - self.assertIsInstance(shadow_subgroup, ShadowGroup) - self.assertIs(shadow_subgroup.shadow, sub_group) + self.assertIs(result.content[0], sub_group) self.assertEqual(result.groups, 1) ui.find.assert_called_once() @@ -235,7 +236,7 @@ def test_get_shadow_sub_group_defined_when_true(self): Given a group with an include and the include resolves to a group that has defined_when evaluate to True When get_shadow is called - Then it returns a shadow group with a shadow group for the sub-group + Then it returns a shadow group containing the subgroup """ sub_group = Group(Item('x'), defined_when="True") group = Group(Include('test_include')) @@ -249,9 +250,7 @@ def test_get_shadow_sub_group_defined_when_true(self): self.assertIsInstance(result, ShadowGroup) self.assertIs(result.shadow, group) self.assertEqual(len(result.content), 1) - shadow_subgroup = result.content[0] - self.assertIsInstance(shadow_subgroup, ShadowGroup) - self.assertIs(shadow_subgroup.shadow, sub_group) + self.assertIs(result.content[0], sub_group) self.assertEqual(result.groups, 1) ui.find.assert_called_once() ui.eval_when.assert_called_once() @@ -279,6 +278,132 @@ def test_get_shadow_sub_group_defined_when_false(self): ui.find.assert_called_once() ui.eval_when.assert_called_once() + def test_get_content_all_items(self): + """ + Given a Group with only Items + When get_content is called + Then it returns the list of Items + """ + item_x = Item('x') + item_y = Item('y') + group = Group(item_x, item_y) + + result = group.get_content() + + self.assertEqual(len(result), 2) + self.assertIs(result[0], item_x) + self.assertIs(result[1], item_y) + + def test_get_content_all_subgroups_allow_groups(self): + """ + Given a Group with only Groups + When get_content is called with allow_groups + Then it returns the list of Groups + """ + item_x = Item('x') + group_x = Group(item_x) + item_y = Item('y') + group_y = Group(item_y) + group = Group(group_x, group_y) + + result = group.get_content() + + self.assertEqual(len(result), 2) + self.assertIs(result[0], group_x) + self.assertIs(result[1], group_y) + + def test_get_content_mixed_allow_groups(self): + """ + Given a Group with a mixture of Groups and Items + When get_content is called with allow_groups + Then it assembles runs of items into ShadowGroups + """ + item_x = Item('x') + group_x = Group(item_x) + item_y = Item('y') + group_y = Group(item_y) + item_z = Item('z') + group = Group(group_x, item_z, group_y) + + result = group.get_content() + + self.assertEqual(len(result), 3) + self.assertIs(result[0], group_x) + self.assertIsInstance(result[1], ShadowGroup) + shadow_group_z = result[1] + self.assertIs(shadow_group_z.shadow, group) + self.assertEqual(len(shadow_group_z.content), 1) + self.assertIs(shadow_group_z.content[0], item_z) + self.assertIs(result[2], group_y) + + def test_get_content_mixed_allow_groups_layout_not_normal(self): + """ + Given a Group with a mixture of Groups and Items and non-normal layout + When get_content is called with allow_groups + Then it returns the contents as-is + """ + item_x = Item('x') + group_x = Group(item_x) + item_y = Item('y') + group_y = Group(item_y) + item_z = Item('z') + group = Group(group_x, item_z, group_y, layout='tabbed') + + result = group.get_content() + + self.assertEqual(len(result), 3) + self.assertIs(result[0], group_x) + self.assertIs(result[1], item_z) + self.assertIs(result[2], group_y) + + def test_get_content_all_subgroups_allow_groups_false(self): + """ + Given a Group with only Groups + When get_content is called with allow_groups False + Then it returns the flattened list of items. + """ + item_x = Item('x') + group_x = Group(item_x) + item_y = Item('y') + group_y = Group(item_y) + group = Group(group_x, group_y) + + result = group.get_content(False) + + self.assertEqual(len(result), 2) + self.assertIs(result[0], item_x) + self.assertIs(result[1], item_y) + + def test_get_content_mixed_allow_groups_false(self): + """ + Given a Group with a mix of Groups and items + When get_content is called with allow_groups False + Then it returns the flattened list of items. + """ + item_x = Item('x') + group_x = Group(item_x) + item_y = Item('y') + group_y = Group(item_y) + item_z = Item('z') + group = Group(group_x, item_z, group_y) + + result = group.get_content(False) + + self.assertEqual(len(result), 3) + self.assertIs(result[0], item_x) + self.assertIs(result[1], item_z) + self.assertIs(result[2], item_y) + + def test_groups_property(self): + item_x = Item('x') + group_x = Group(item_x) + item_y = Item('y') + group_y = Group(item_y) + item_z = Item('z') + group = Group(group_x, item_z, group_y) + + self.assertEqual(group.groups, 2) + class TestShadowGroup(BaseTestMixin, unittest.TestCase): @@ -294,7 +419,6 @@ def test_get_content_all_items(self): shadow_group = ShadowGroup( shadow=group, content=group.content, - groups=0, ) result = shadow_group.get_content() @@ -305,63 +429,49 @@ def test_get_content_all_items(self): def test_get_content_all_subgroups_allow_groups(self): """ - Given a ShadowGroup with only Groups + Given a ShadowGroup with only Groups and ShadowGroups When get_content is called with allow_groups - Then it returns the list of Groups + Then it returns the list of Groups and ShadowGroups """ item_x = Item('x') group_x = Group(item_x) shadow_group_x = ShadowGroup( shadow=group_x, content=group_x.content, - groups=0, ) item_y = Item('y') group_y = Group(item_y) - shadow_group_y = ShadowGroup( - shadow=group_y, - content=group_y.content, - groups=0, - ) group = Group(group_x, group_y) shadow_group = ShadowGroup( shadow=group, - content=[shadow_group_x, shadow_group_y], - groups=2, + content=[shadow_group_x, group_y], ) result = shadow_group.get_content() self.assertEqual(len(result), 2) self.assertIs(result[0], shadow_group_x) - self.assertIs(result[1], shadow_group_y) + self.assertIs(result[1], group_y) def test_get_content_mixed_allow_groups(self): """ - Given a ShadowGroup with only Groups + Given a ShadowGroup with a mix of Groups, ShadowGroups and Items When get_content is called with allow_groups - Then it assembles runs of items into groups + Then it assembles runs of items into ShadowGroups """ item_x = Item('x') group_x = Group(item_x) shadow_group_x = ShadowGroup( shadow=group_x, content=group_x.content, - groups=0, ) item_y = Item('y') group_y = Group(item_y) - shadow_group_y = ShadowGroup( - shadow=group_y, - content=group_y.content, - groups=0, - ) item_z = Item('z') group = Group(group_x, item_z, group_y) shadow_group = ShadowGroup( shadow=group, - content=[shadow_group_x, item_z, shadow_group_y], - groups=2, + content=[shadow_group_x, item_z, group_y], ) result = shadow_group.get_content() @@ -373,11 +483,40 @@ def test_get_content_mixed_allow_groups(self): self.assertIs(shadow_group_z.shadow, group) self.assertEqual(len(shadow_group_z.content), 1) self.assertIs(shadow_group_z.content[0], item_z) - self.assertIs(result[2], shadow_group_y) + self.assertIs(result[2], group_y) + + def test_get_content_mixed_allow_groups_layout_not_normal(self): + """ + Given a ShadowGroup with a mixture of Groups, ShadowGroups and Items + and non-normal layout + When get_content is called with allow_groups + Then it returns the contents as-is + """ + item_x = Item('x') + group_x = Group(item_x) + shadow_group_x = ShadowGroup( + shadow=group_x, + content=group_x.content, + ) + item_y = Item('y') + group_y = Group(item_y) + item_z = Item('z') + group = Group(group_x, item_z, group_y, layout='tabbed') + shadow_group = ShadowGroup( + shadow=group, + content=[shadow_group_x, item_z, group_y], + ) + + result = shadow_group.get_content() + + self.assertEqual(len(result), 3) + self.assertIs(result[0], shadow_group_x) + self.assertIs(result[1], item_z) + self.assertIs(result[2], group_y) def test_get_content_all_subgroups_allow_groups_false(self): """ - Given a ShadowGroup with only Groups + Given a ShadowGroup with only Groups and ShadowGroups When get_content is called with allow_groups False Then it returns the flattened list of items. """ @@ -386,20 +525,13 @@ def test_get_content_all_subgroups_allow_groups_false(self): shadow_group_x = ShadowGroup( shadow=group_x, content=group_x.content, - groups=0, ) item_y = Item('y') group_y = Group(item_y) - shadow_group_y = ShadowGroup( - shadow=group_y, - content=group_y.content, - groups=0, - ) group = Group(group_x, group_y) shadow_group = ShadowGroup( shadow=group, - content=[shadow_group_x, shadow_group_y], - groups=2, + content=[shadow_group_x, group_y], ) result = shadow_group.get_content(False) @@ -408,10 +540,10 @@ def test_get_content_all_subgroups_allow_groups_false(self): self.assertIs(result[0], item_x) self.assertIs(result[1], item_y) - def test_get_content_mixed_allow_groups(self): + def test_get_content_mixed_allow_groups_false(self): """ - Given a ShadowGroup with only Groups - When get_content is called with allow_groups + Given a ShadowGroup with a mix of Groups, ShadowGroups and items + When get_content is called with allow_groups False Then it returns the flattened list of items. """ item_x = Item('x') @@ -419,21 +551,14 @@ def test_get_content_mixed_allow_groups(self): shadow_group_x = ShadowGroup( shadow=group_x, content=group_x.content, - groups=0, ) item_y = Item('y') group_y = Group(item_y) - shadow_group_y = ShadowGroup( - shadow=group_y, - content=group_y.content, - groups=0, - ) item_z = Item('z') group = Group(group_x, item_z, group_y) shadow_group = ShadowGroup( shadow=group, - content=[shadow_group_x, item_z, shadow_group_y], - groups=2, + content=[shadow_group_x, item_z, group_y], ) result = shadow_group.get_content(False) From 1a0085b8bb75d5d35270e3227bbca42db04a4a4e Mon Sep 17 00:00:00 2001 From: Corran Webster Date: Mon, 6 Sep 2021 17:24:38 +0100 Subject: [PATCH 4/4] Fix flake8 issues and duplicated test method names. --- traitsui/tests/test_group.py | 40 ++++-------------------------------- 1 file changed, 4 insertions(+), 36 deletions(-) diff --git a/traitsui/tests/test_group.py b/traitsui/tests/test_group.py index 4f7d27482..0fc3ad74c 100644 --- a/traitsui/tests/test_group.py +++ b/traitsui/tests/test_group.py @@ -12,44 +12,12 @@ Test cases for the UI object. """ -import contextlib -from re import sub import unittest import unittest.mock -from pyface.api import GUI -from traits.api import Property -from traits.has_traits import HasTraits, HasStrictTraits -from traits.trait_types import Str, Int - -from traitsui.basic_editor_factory import BasicEditorFactory -from traitsui.api import Group, Include, Item, spring, View +from traitsui.api import Group, Include, Item from traitsui.group import ShadowGroup -from traitsui.tests._tools import ( - BaseTestMixin, - count_calls, - create_ui, - is_qt, - is_wx, - process_cascade_events, - requires_toolkit, - reraise_exceptions, - ToolkitName, -) -from traitsui.toolkit import toolkit, toolkit_object - - -class BaseWithInclude(HasTraits): - - x = Str() - - traits_view = View( - Include('included_group'), - ) - -class SubclassWithInclude(BaseWithInclude): - - included_group = Group('x') +from traitsui.tests._tools import BaseTestMixin class TestGroup(BaseTestMixin, unittest.TestCase): @@ -231,7 +199,7 @@ def test_get_shadow_include_sub_group(self): self.assertEqual(result.groups, 1) ui.find.assert_called_once() - def test_get_shadow_sub_group_defined_when_true(self): + def test_get_shadow_include_sub_group_defined_when_true(self): """ Given a group with an include and the include resolves to a group that has defined_when evaluate to True @@ -255,7 +223,7 @@ def test_get_shadow_sub_group_defined_when_true(self): ui.find.assert_called_once() ui.eval_when.assert_called_once() - def test_get_shadow_sub_group_defined_when_false(self): + def test_get_shadow_include_sub_group_defined_when_false(self): """ Given a group with an include and the include resolves to a group that has defined_when evaluate to True