From df780117a66b988bd188bc82cd313984af8af97e Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 19 Jun 2023 14:26:18 +0800 Subject: [PATCH] Core tests converted to Pytest, with 100% coverage. --- changes/1996.removal.2.rst | 2 +- core/src/toga/widgets/base.py | 5 - core/src/toga/widgets/optioncontainer.py | 54 +- core/tests/widgets/test_optioncontainer.py | 719 +++++++++++------- demo/toga_demo/app.py | 10 +- .../src/toga_dummy/widgets/optioncontainer.py | 28 +- .../optioncontainer/optioncontainer/app.py | 6 +- 7 files changed, 466 insertions(+), 358 deletions(-) diff --git a/changes/1996.removal.2.rst b/changes/1996.removal.2.rst index 61f5b0b8c0..7b1b2a7c64 100644 --- a/changes/1996.removal.2.rst +++ b/changes/1996.removal.2.rst @@ -1 +1 @@ -``OptionContainer.add()`` has been renamed ``OptionContainer.append()`` for consistency with List APIs. +``OptionContainer.add()``, ``OptionContainer.remove()`` and ``OptionContainer.insert()`` have been removed, due to being ambiguous with base widget methods of the same name. Use the ``OptionContainer.content.append()``, ``OptionContainer.content.remove()`` and ``OptionContainer.content.insert()`` APIs instead. diff --git a/core/src/toga/widgets/base.py b/core/src/toga/widgets/base.py index b0d2f8e1e5..d8033bd715 100644 --- a/core/src/toga/widgets/base.py +++ b/core/src/toga/widgets/base.py @@ -262,16 +262,11 @@ def refresh(self): # defer the refresh call to the root node. self._root.refresh() else: - self.refresh_sublayouts() # We can't compute a layout until we have a viewport if self._impl.viewport: super().refresh(self._impl.viewport) self._impl.viewport.refreshed() - def refresh_sublayouts(self): - for child in self.children: - child.refresh_sublayouts() - def focus(self): """Give this widget the input focus. diff --git a/core/src/toga/widgets/optioncontainer.py b/core/src/toga/widgets/optioncontainer.py index b772d824ec..35dabac593 100644 --- a/core/src/toga/widgets/optioncontainer.py +++ b/core/src/toga/widgets/optioncontainer.py @@ -51,9 +51,6 @@ def content(self) -> Widget: """The content widget displayed in this tab of the OptionContainer.""" return self._content - def refresh(self): - self._content.refresh() - class OptionList: def __init__(self, interface): @@ -171,7 +168,7 @@ def insert( # The option now exists on the implementation; # finalize the display properties that can't be resolved until the # implementation exists. - widget.refresh() + self.interface.refresh() item.enabled = enabled @@ -203,7 +200,7 @@ def __init__( if content: for text, widget in content: - self.append(text, widget) + self.content.append(text, widget) self.on_select = on_select @@ -231,18 +228,11 @@ def content(self) -> OptionList: @property def current_tab(self) -> OptionItem: - """The currently selected item of content. - - When setting the current item, you can use: - - * The integer index of the item - - * An OptionItem reference - - * The string label of the item. The first item whose label matches - will be selected. - """ - return self._content[self._impl.get_current_tab_index()] + """The currently selected tab of content.""" + index = self._impl.get_current_tab_index() + if index is None: + return None + return self._content[index] @current_tab.setter def current_tab(self, value): @@ -267,36 +257,6 @@ def window(self, window): for item in self._content: item._content.window = window - def append(self, text: str, widget: Widget): - """Append a new tab of content to the OptionContainer. - - :param text: The text label for the new tab - :param widget: The content widget to use for the new tab. - """ - self._content.append(text, widget) - - def insert(self, index: int | str | OptionItem, text: str, widget: Widget): - """Insert a new tab of content to the OptionContainer at the specified index. - - :param index: The index where the new item should be inserted (or a specifier - that can be converted into an index). - :param text: The text label for the new tab. - :param widget: The content widget to use for the new tab. - """ - self._content.insert(index, text, widget) - - def remove(self, item: int | str | OptionItem): - """Remove a tab of content from the OptionContainer. - - :param item: The tab of content to remove. - """ - self._content.remove(item) - - def refresh_sublayouts(self): - """Refresh the layout and appearance of this widget.""" - for widget in self._content: - widget.refresh() - @property def on_select(self) -> callable: """The callback to invoke when a new tab of content is selected.""" diff --git a/core/tests/widgets/test_optioncontainer.py b/core/tests/widgets/test_optioncontainer.py index 0dbef38fde..0af03e070c 100644 --- a/core/tests/widgets/test_optioncontainer.py +++ b/core/tests/widgets/test_optioncontainer.py @@ -1,284 +1,439 @@ -from unittest import mock +from unittest.mock import Mock + +import pytest import toga -from toga_dummy.utils import TestCase, TestStyle - - -class OptionContainerTests(TestCase): - def setUp(self): - super().setUp() - - self.on_select = mock.Mock() - self.op_container = toga.OptionContainer( - style=TestStyle(), on_select=self.on_select - ) - self.widget = toga.Box(style=TestStyle()) - self.text2, self.widget2 = "Widget 2", toga.Box(style=TestStyle()) - self.text3, self.widget3 = "Widget 3", toga.Box(style=TestStyle()) - self.text = "New Container" - self.op_container.add(self.text, self.widget) - - def assert_tab(self, tab, index, text, widget, enabled): - self.assertEqual(tab.index, index) - self.assertEqual(tab.text, text) - self.assertEqual(tab._interface, self.op_container) - self.assertEqual(tab.enabled, enabled) - self.assertEqual(tab.content, widget) - - def add_widgets(self): - self.op_container.add(self.text2, self.widget2) - self.op_container.add(self.text3, self.widget3) - - def test_on_select(self): - self.assertEqual(self.op_container.on_select._raw, self.on_select) - - def test_widget_created(self): - self.assertEqual(self.op_container._impl.interface, self.op_container) - self.assertActionPerformed(self.op_container, "create OptionContainer") - - def test_adding_container_invokes_add_content(self): - self.assertActionPerformedWith( - self.op_container, "add content", text=self.text, widget=self.widget._impl - ) - - def test_widget_refresh_sublayouts(self): - # Clear event log to verify new set bounds for refresh - self.reset_event_log() - - self.op_container.refresh_sublayouts() - - def test_set_current_tab_as_index(self): - self.add_widgets() - self.op_container.current_tab = 1 - self.assert_tab( - self.op_container.current_tab, - index=1, - text=self.text2, - widget=self.widget2, - enabled=True, - ) - - def test_set_current_tab_as_label(self): - self.add_widgets() - self.op_container.current_tab = self.text3 - self.assert_tab( - self.op_container.current_tab, - index=2, - text=self.text3, - widget=self.widget3, - enabled=True, - ) - - def test_set_current_tab_as_tab(self): - self.add_widgets() - self.op_container.current_tab = self.op_container.content[1] - self.assert_tab( - self.op_container.current_tab, - index=1, - text=self.text2, - widget=self.widget2, - enabled=True, - ) - - def test_current_tab_increment(self): - self.add_widgets() - self.op_container.current_tab = 1 - self.op_container.current_tab += 1 - self.assert_tab( - self.op_container.current_tab, - index=2, - text=self.text3, - widget=self.widget3, - enabled=True, - ) - - def test_set_current_tab_as_text_raises_an_error(self): - self.add_widgets() - - def set_text(): - self.op_container.current_tab = "I do not exist!" - - self.assertRaises(ValueError, set_text) - - def test_current_tab_string_increment_raises_an_error(self): - self.add_widgets() - - def set_text(): - self.op_container.current_tab += "I do not exist!" - - self.assertRaises(ValueError, set_text) - - def test_current_tab_string_decrement_raises_an_error(self): - self.add_widgets() - - def set_text(): - self.op_container.current_tab -= "I do not exist!" - - self.assertRaises(ValueError, set_text) - - def test_current_tab_decrement(self): - self.add_widgets() - self.op_container.current_tab = 1 - self.op_container.current_tab -= 1 - self.assert_tab( - self.op_container.current_tab, - index=0, - text=self.text, - widget=self.widget, - enabled=True, - ) - - def test_disable_tab(self): - self.op_container.current_tab.enabled = False - self.assertEqual(self.op_container.current_tab.enabled, False) - - def test_content_repr(self): - self.add_widgets() - self.assertEqual( - ( - "OptionList([OptionItem(title=New Container), " - "OptionItem(title=Widget 2), " - "OptionItem(title=Widget 3)])" - ), - repr(self.op_container.content), - ) - - def test_add_tabs(self): - self.add_widgets() - self.assertEqual(len(self.op_container.content), 3) - self.assertEqual(self.op_container.content[0]._content, self.widget) - self.assertEqual(self.op_container.content[1]._content, self.widget2) - self.assertEqual(self.op_container.content[2]._content, self.widget3) - - def test_remove_tab(self): - self.add_widgets() - self.op_container.remove(1) - self.assertEqual(len(self.op_container.content), 2) - self.assertEqual(self.op_container.content[0]._content, self.widget) - self.assertEqual(self.op_container.content[1]._content, self.widget3) - - def test_set_content_in_constructor(self): - new_container = toga.OptionContainer( - style=TestStyle(), - content=[ - (self.text, self.widget), - (self.text2, self.widget2), - (self.text3, self.widget3), - ], - ) - self.assertEqual(len(new_container.content), 3) - self.assertEqual(new_container.content[0]._content, self.widget) - self.assertEqual(new_container.content[1]._content, self.widget2) - self.assertEqual(new_container.content[2]._content, self.widget3) - - def test_set_window(self): - window = mock.Mock() - self.op_container.window = window - for item in self.op_container.content: - self.assertEqual(item._content.window, window) - - def test_set_tab_title(self): - new_text = "New Title" - self.op_container.content[0].text = new_text - self.assertEqual(self.op_container.content[0].text, new_text) - - def test_insert_tab(self): - self.op_container.insert(0, text=self.text2, widget=self.widget2) - self.assertEqual(self.op_container.content[0].text, self.text2) - - def test_set_app(self): - app = mock.Mock() - self.op_container.app = app - for item in self.op_container.content: - self.assertEqual(item._content.app, app) - - ###################################################################### - # 2022-07: Backwards compatibility - ###################################################################### - - def test_tab_label_deprecated(self): - new_text = "New Text" - with self.assertWarns(DeprecationWarning): - self.assertEqual(self.op_container.current_tab.label, self.text) - with self.assertWarns(DeprecationWarning): - self.op_container.current_tab.label = new_text - self.assertEqual(self.op_container.current_tab.text, new_text) - - def test_add_tab_deprecated(self): - # label is a deprecated argument - with self.assertWarns(DeprecationWarning): - self.op_container.add(label=self.text2, widget=self.widget2) - self.assertEqual(self.op_container.content[1].text, self.text2) - - # can't specify both label *and* text - with self.assertRaises(ValueError): - self.op_container.add( - text=self.text3, widget=self.widget3, label=self.text3 - ) - - def test_append_tab_deprecated(self): - # label is a deprecated argument - with self.assertWarns(DeprecationWarning): - self.op_container.content.append(label=self.text2, widget=self.widget2) - self.assertEqual(self.op_container.content[1].text, self.text2) - - # can't specify both label *and* text - with self.assertRaises(ValueError): - self.op_container.content.append( - text=self.text3, widget=self.widget3, label=self.text3 - ) - - def test_insert_tab_deprecated(self): - # label is a deprecated argument - with self.assertWarns(DeprecationWarning): - self.op_container.content.insert(1, label=self.text2, widget=self.widget2) - self.assertEqual(self.op_container.content[1].text, self.text2) - - # can't specify both label *and* text - with self.assertRaises(ValueError): - self.op_container.content.insert( - 1, text=self.text3, widget=self.widget3, label=self.text3 - ) - - def test_add_mandatory_parameters(self): - my_op_container = toga.OptionContainer( - style=TestStyle(), on_select=self.on_select - ) - - # text and widget parameters are mandatory - with self.assertRaises(TypeError): - my_op_container.add() - with self.assertRaises(TypeError): - my_op_container.add(self.text) - with self.assertRaises(TypeError): - my_op_container.add(widget=self.widget) - - def test_append_mandatory_parameters(self): - my_op_container = toga.OptionContainer( - style=TestStyle(), on_select=self.on_select - ) - - # text and widget parameters are mandatory - with self.assertRaises(TypeError): - my_op_container.content.append() - with self.assertRaises(TypeError): - my_op_container.content.append(self.text) - with self.assertRaises(TypeError): - my_op_container.content.append(widget=self.widget) - - def test_insert_mandatory_parameters(self): - my_op_container = toga.OptionContainer( - style=TestStyle(), on_select=self.on_select - ) - - # text and widget parameters are mandatory - with self.assertRaises(TypeError): - my_op_container.content.insert(0) - with self.assertRaises(TypeError): - my_op_container.content.insert(0, self.text) - with self.assertRaises(TypeError): - my_op_container.content.insert(0, widget=self.widget) - - ###################################################################### - # End backwards compatibility. - ###################################################################### +from toga_dummy.utils import ( + assert_action_not_performed, + assert_action_performed, + assert_action_performed_with, +) + + +@pytest.fixture +def app(): + return toga.App("Option Container Test", "org.beeware.toga.option_container") + + +@pytest.fixture +def window(): + return toga.Window() + + +@pytest.fixture +def content1(): + return toga.Box() + + +@pytest.fixture +def content2(): + return toga.Box() + + +@pytest.fixture +def content3(): + return toga.Box() + + +@pytest.fixture +def on_select_handler(): + return Mock() + + +@pytest.fixture +def optioncontainer(content1, content2, content3, on_select_handler): + return toga.OptionContainer( + content=[("Item 1", content1), ("Item 2", content2), ("Item 3", content3)], + on_select=on_select_handler, + ) + + +def test_widget_create(): + "An option container can be created with no arguments" + optioncontainer = toga.OptionContainer() + assert_action_performed(optioncontainer, "create OptionContainer") + + assert len(optioncontainer.content) == 0 + assert optioncontainer.current_tab is None + assert optioncontainer.on_select._raw is None + + +def test_widget_create_with_args(optioncontainer, on_select_handler): + "An option container can be created with arguments" + assert optioncontainer._impl.interface == optioncontainer + assert_action_performed(optioncontainer, "create OptionContainer") + + assert len(optioncontainer.content) == 3 + assert optioncontainer.current_tab.text == "Item 1" + assert optioncontainer.on_select._raw == on_select_handler + + +def test_assign_to_app(app, optioncontainer, content1, content2, content3): + """If the widget is assigned to an app, the content is also assigned""" + # Option container is initially unassigned + assert optioncontainer.app is None + + # Assign the option container to the app + optioncontainer.app = app + + # option container is on the app + assert optioncontainer.app == app + + # Content is also on the app + assert content1.app == app + assert content2.app == app + assert content3.app == app + + +def test_assign_to_app_no_content(app): + """If the widget is assigned to an app, and there is no content, there's no error""" + optioncontainer = toga.OptionContainer() + + # Option container is initially unassigned + assert optioncontainer.app is None + + # Assign the Option container to the app + optioncontainer.app = app + + # Option container is on the app + assert optioncontainer.app == app + + +def test_assign_to_window(window, optioncontainer, content1, content2, content3): + """If the widget is assigned to a window, the content is also assigned""" + # Option container is initially unassigned + assert optioncontainer.window is None + + # Assign the Option container to the window + optioncontainer.window = window + + # Option container is on the window + assert optioncontainer.window == window + # Content is also on the window + assert content1.window == window + assert content2.window == window + assert content3.window == window + + +def test_assign_to_window_no_content(window): + """If the widget is assigned to a window, and there is no content, there's no error""" + optioncontainer = toga.OptionContainer() + + # Option container is initially unassigned + assert optioncontainer.window is None + + # Assign the Option container to the window + optioncontainer.window = window + + # Option container is on the window + assert optioncontainer.window == window + + +def test_disable_no_op(optioncontainer): + """OptionContainer doesn't have a disabled state""" + # Enabled by default + assert optioncontainer.enabled + + # Try to disable the widget + optioncontainer.enabled = False + + # Still enabled. + assert optioncontainer.enabled + + +def test_focus_noop(optioncontainer): + """Focus is a no-op.""" + + optioncontainer.focus() + assert_action_not_performed(optioncontainer, "focus") + + +@pytest.mark.parametrize( + "value, expected", + [ + (None, False), + ("", False), + ("true", True), + ("false", True), # Evaluated as a string, this value is true. + (0, False), + (1234, True), + ], +) +def test_item_enabled(optioncontainer, value, expected): + """The enabled status of an item can be changed.""" + item = optioncontainer.content[1] + + # item is initially enabled by default. + assert item.enabled + + # Set the enabled status + item.enabled = value + assert item.enabled == expected + + # Disable the widget + item.enabled = False + assert not item.enabled + + # Set the enabled status again + item.enabled = value + assert item.enabled == expected + + +class MyTitle: + def __init__(self, title): + self.title = title + + def __str__(self): + return self.title + + +@pytest.mark.parametrize( + "value, expected", + [ + ("New Title", "New Title"), + (42, "42"), # Evaluated as a string + (MyTitle("Custom Title"), "Custom Title"), # Evaluated as a string + ], +) +def test_item_text(optioncontainer, value, expected): + """The title of an item can be changed.""" + item = optioncontainer.content[1] + + # Set the item text + item.text = value + assert item.text == expected + + +@pytest.mark.parametrize( + "value, error", + [ + (None, r"Item text cannot be None"), + ("", r"Item text cannot be blank"), + (MyTitle(""), r"Item text cannot be blank"), + ], +) +def test_invalid_item_text(optioncontainer, value, error): + """Invalid item titles are prevented""" + item = optioncontainer.content[1] + + # Using invalid text raises an error + with pytest.raises(ValueError, match=error): + item.text = value + + +def test_optionlist_repr(optioncontainer): + """OptionContainer content has a helpful repr""" + assert repr(optioncontainer.content) == "" + + +def test_optionlist_iter(optioncontainer): + """OptionContainer content can be iterated""" + assert [item.text for item in optioncontainer.content] == [ + "Item 1", + "Item 2", + "Item 3", + ] + + +def test_optionlist_len(optioncontainer): + """OptionContainer content has length""" + assert len(optioncontainer.content) == 3 + + +@pytest.mark.parametrize("index", [1, "Item 2", None]) +def test_getitem(optioncontainer, content2, index): + """An item can be retrieved""" + if index is None: + index = optioncontainer.content[1] + + # get item + item = optioncontainer.content[index] + assert item.text == "Item 2" + assert item.index == 1 + assert item.content == content2 + + +@pytest.mark.parametrize("index", [1, "Item 2", None]) +def test_delitem(optioncontainer, index): + """An item can be removed with __del__""" + if index is None: + index = optioncontainer.content[1] + + # get a reference to items 1 and 3 + item1 = optioncontainer.content[0] + item3 = optioncontainer.content[2] + + # delete item + del optioncontainer.content[index] + assert len(optioncontainer.content) == 2 + assert_action_performed_with(optioncontainer, "remove content", index=1) + + # There's no item with the deleted label + with pytest.raises(ValueError, match=r"No tab named 'Item 2'"): + optioncontainer.content.index("Item 2") + + # The index of item 3 has been reduced; item 1 is untouched + assert item1.index == 0 + assert item3.index == 1 + + # Widget has been refreshed + assert_action_performed(optioncontainer, "refresh") + + +@pytest.mark.parametrize("index", [0, "Item 1", None]) +def test_delitem_current(optioncontainer, index): + """The current item can't be deleted""" + if index is None: + index = optioncontainer.content[0] + + with pytest.raises( + ValueError, match=r"The currently selected tab cannot be deleted." + ): + del optioncontainer.content[index] + + +@pytest.mark.parametrize("index", [1, "Item 2", None]) +def test_item_remove(optioncontainer, index): + """An item can be removed with remove""" + if index is None: + index = optioncontainer.content[1] + + # get a reference to items 1 and 3 + item1 = optioncontainer.content[0] + item3 = optioncontainer.content[2] + + # remove item + optioncontainer.content.remove(index) + assert len(optioncontainer.content) == 2 + assert_action_performed_with(optioncontainer, "remove content", index=1) + + # There's no item with the deleted label + with pytest.raises(ValueError, match=r"No tab named 'Item 2'"): + optioncontainer.content.index("Item 2") + + # The index of item 3 has been reduced; item 1 is untouched + assert item1.index == 0 + assert item3.index == 1 + + # Widget has been refreshed + assert_action_performed(optioncontainer, "refresh") + + +@pytest.mark.parametrize("index", [0, "Item 1", None]) +def test_item_remove_current(optioncontainer, index): + """The current item can't be removed""" + if index is None: + index = optioncontainer.content[0] + + with pytest.raises( + ValueError, match=r"The currently selected tab cannot be deleted." + ): + optioncontainer.content.remove(index) + + +@pytest.mark.parametrize( + "value, expected", + [ + ("New Title", "New Title"), + (42, "42"), # Evaluated as a string + (MyTitle("Custom Title"), "Custom Title"), # Evaluated as a string + ], +) +def test_item_insert_text(optioncontainer, value, expected): + """The text of an inserted item can be set""" + new_content = toga.Box() + + optioncontainer.content.insert(1, value, new_content, enabled=True) + + # Backend added an item and set enabled + assert_action_performed_with( + optioncontainer, + "add content", + index=1, + text=expected, + widget=new_content._impl, + ) + assert_action_performed_with( + optioncontainer, + "set option enabled", + index=1, + value=True, + ) + assert_action_performed_with(optioncontainer, "refresh") + + +@pytest.mark.parametrize( + "value, error", + [ + (None, r"Item text cannot be None"), + ("", r"Item text cannot be blank"), + (MyTitle(""), r"Item text cannot be blank"), + ], +) +def test_item_insert_invalid_text(optioncontainer, value, error): + """The item text must be valid""" + new_content = toga.Box() + with pytest.raises(ValueError, match=error): + optioncontainer.content.insert(1, value, new_content, enabled=True) + + +@pytest.mark.parametrize("enabled", [True, False]) +def test_item_insert_enabled(optioncontainer, enabled): + """The enabled status of content can be set""" + new_content = toga.Box() + + optioncontainer.content.insert(1, "New content", new_content, enabled=enabled) + + # Backend added an item and set enabled + assert_action_performed_with( + optioncontainer, + "add content", + index=1, + text="New content", + widget=new_content._impl, + ) + assert_action_performed_with( + optioncontainer, + "set option enabled", + index=1, + value=enabled, + ) + assert_action_performed_with(optioncontainer, "refresh") + + +@pytest.mark.parametrize("enabled", [True, False]) +def test_item_append(optioncontainer, enabled): + """An item can be appended to the content list""" + # append is implemented using insert; + # the bulk of the functionality is tested there. + new_content = toga.Box() + + optioncontainer.content.append("New content", new_content, enabled=enabled) + assert_action_performed_with( + optioncontainer, "add content", index=3, widget=new_content._impl + ) + assert_action_performed_with( + optioncontainer, "set option enabled", index=3, value=enabled + ) + assert_action_performed_with(optioncontainer, "refresh") + + +@pytest.mark.parametrize("index", [1, "Item 2", None]) +def test_current_tab(optioncontainer, index, on_select_handler): + """The current tab of the optioncontainer can be changed.""" + if index is None: + index = optioncontainer.content[1] + + # First item is selected initially + assert optioncontainer.current_tab.index == 0 + assert optioncontainer.current_tab.text == "Item 1" + + # Programatically select item 2 + optioncontainer.current_tab = index + + # Current tab values have changed + assert optioncontainer.current_tab.index == 1 + assert optioncontainer.current_tab.text == "Item 2" + + # on_select handler was invoked + on_select_handler.assert_called_once_with(optioncontainer) diff --git a/demo/toga_demo/app.py b/demo/toga_demo/app.py index 261ccc5579..b4e020bb5d 100755 --- a/demo/toga_demo/app.py +++ b/demo/toga_demo/app.py @@ -8,8 +8,6 @@ def startup(self): # Create the main window self.main_window = toga.MainWindow(self.name) - left_container = toga.OptionContainer() - left_table = toga.Table( headings=["Hello", "World"], data=[ @@ -35,8 +33,12 @@ def startup(self): }, ) - left_container.add("Table", left_table) - left_container.add("Tree", left_tree) + left_container = toga.OptionContainer( + content=[ + ("Table", left_table), + ("Tree", left_tree), + ] + ) right_content = toga.Box(style=Pack(direction=COLUMN)) for b in range(0, 10): diff --git a/dummy/src/toga_dummy/widgets/optioncontainer.py b/dummy/src/toga_dummy/widgets/optioncontainer.py index 4ee263b262..d35e02d710 100644 --- a/dummy/src/toga_dummy/widgets/optioncontainer.py +++ b/dummy/src/toga_dummy/widgets/optioncontainer.py @@ -10,48 +10,44 @@ def __init__(self, text, widget, enabled): self.enabled = enabled +@not_required # Testbed coverage is complete for this widget. class OptionContainer(Widget): def create(self): self._action("create OptionContainer") self._items = [] - self._current_index = 0 def add_content(self, index, text, widget): self._action("add content", index=index, text=text, widget=widget) self._items.insert(index, Option(text, widget, True)) + # if this is the first item of content, set it as the selected item. + if len(self._items) == 1: + self.set_current_tab_index(0) + def remove_content(self, index): - if index == self._current_index: - # Don't allow removal of a selected tab - raise self.interface.OptionException( - "Currently selected option cannot be removed" - ) self._action("remove content", index=index) del self._items[index] - def set_on_select(self, handler): - self._set_value("on_select", handler) - def set_option_enabled(self, index, enabled): - self._set_value(f"option_{index}_enabled", value=enabled) + self._action("set option enabled", index=index, value=enabled) self._items[index].enabled = enabled def is_option_enabled(self, index): - self._get_value(f"option_{index}_enabled", None) return self._items[index].enabled def set_option_text(self, index, value): - self._set_value(f"option_{index}_text", value=value) + self._action("set option text", index=index, value=value) self._items[index].text = value def get_option_text(self, index): - self._get_value(f"option_{index}_text", None) return self._items[index].text def set_current_tab_index(self, current_tab_index): self._set_value("current_tab_index", current_tab_index) - self._current_index = current_tab_index + self.interface.on_select(None) def get_current_tab_index(self): - self._get_value("current_tab_index", 0) - return self._current_index + return self._get_value("current_tab_index", None) + + def simulate_select_tab(self, index): + self.set_current_tab_index(index) diff --git a/examples/optioncontainer/optioncontainer/app.py b/examples/optioncontainer/optioncontainer/app.py index e40a597b55..cc035cd488 100644 --- a/examples/optioncontainer/optioncontainer/app.py +++ b/examples/optioncontainer/optioncontainer/app.py @@ -13,9 +13,9 @@ def _create_options(self): box1 = toga.Box(children=[label_box1]) box2 = toga.Box(children=[label_box2]) - self.optioncontainer.add("Option 0", box0) - self.optioncontainer.add("Option 1", box1) - self.optioncontainer.add("Option 2", box2) + self.optioncontainer.content.append("Option 0", box0) + self.optioncontainer.content.append("Option 1", box1) + self.optioncontainer.content.append("Option 2", box2) self._refresh_select() def _refresh_select(self):