Browse Source

closes #517 - radio group not enabling widget when initial_selection is set

1.0.4-release--issue-470
Chris 4 years ago
parent
commit
91d0fad421
5 changed files with 258 additions and 10 deletions
  1. 7
      gooey/gui/application.py
  2. 18
      gooey/gui/components/widgets/radio_group.py
  3. 32
      gooey/python_bindings/gooey_decorator.py
  4. 22
      gooey/tests/harness.py
  5. 189
      gooey/tests/test_radiogroup.py

7
gooey/gui/application.py

@ -18,7 +18,7 @@ from gooey.util.functional import merge
def run(build_spec):
app = build_app(build_spec)
app, _ = build_app(build_spec)
app.MainLoop()
@ -29,8 +29,5 @@ def build_app(build_spec):
imagesPaths = image_repository.loadImages(build_spec['image_dir'])
gapp = GooeyApplication(merge(build_spec, imagesPaths))
gapp.Show()
return app
return (app, gapp)

18
gooey/gui/components/widgets/radio_group.py

@ -20,7 +20,7 @@ class RadioGroup(BaseWidget):
self.selected = None
self.widgets = self.createWidgets()
self.arrange()
self.applyStyleRules()
for button in self.radioButtons:
button.Bind(wx.EVT_LEFT_DOWN, self.handleButtonClick)
@ -31,6 +31,8 @@ class RadioGroup(BaseWidget):
self.selected.SetValue(True)
self.handleImplicitCheck()
self.applyStyleRules()
def getValue(self):
for button, widget in zip(self.radioButtons, self.widgets):
@ -85,6 +87,7 @@ class RadioGroup(BaseWidget):
# if it is not in the required section, allow it to be deselected
if not self.widgetInfo['required']:
self.selected.SetValue(False)
self.selected = None
self.applyStyleRules()
self.handleImplicitCheck()
@ -104,9 +107,9 @@ class RadioGroup(BaseWidget):
if isinstance(widget, CheckBox):
widget.hideInput()
if not button.GetValue(): # not checked
widget.widget.Disable()
widget.Disable()
else:
widget.widget.Enable()
widget.Enable()
def handleImplicitCheck(self):
"""
@ -141,5 +144,10 @@ class RadioGroup(BaseWidget):
Instantiate the Gooey Widgets that are used within the RadioGroup
"""
from gooey.gui.components import widgets
return [getattr(widgets, item['type'])(self, item)
for item in getin(self.widgetInfo, ['data', 'widgets'], [])]
widgets = [getattr(widgets, item['type'])(self, item)
for item in getin(self.widgetInfo, ['data', 'widgets'], [])]
# widgets should be disabled unless
# explicitly selected
for widget in widgets:
widget.Disable()
return widgets

32
gooey/python_bindings/gooey_decorator.py

@ -18,6 +18,38 @@ from . import cmd_args
IGNORE_COMMAND = '--ignore-gooey'
# TODO: use these defaults in the decorator and migrate to a flat **kwargs
# They're pulled out here for wiring up instances in the tests.
# Some fiddling is needed before I can make the changes to make the swap to
# `defaults` + **kwargs overrides.
defaults = {
'advanced': True,
'language': 'english',
'auto_start': False, # TODO: add this to the docs. Used to be `show_config=True`
'target': None,
'program_name': None,
'program_description': None,
'default_size': (610, 530),
'use_legacy_titles': True,
'required_cols': 2,
'optional_cols': 2,
'dump_build_config': False,
'load_build_config': None,
'monospace_display': False, # TODO: add this to the docs
'image_dir': '::gooey/default',
'language_dir': getResourcePath('languages'),
'progress_regex': None, # TODO: add this to the docs
'progress_expr': None, # TODO: add this to the docs
'hide_progress_msg': False, # TODO: add this to the docs
'disable_progress_bar_animation': False,
'disable_stop_button': False,
'group_by_type': True,
'header_height': 80,
'navigation': 'SIDEBAR', # TODO: add this to the docs
'tabbed_groups': False,
'use_cmd_args': False,
}
# TODO: kwargs all the things
def Gooey(f=None,
advanced=True,

22
gooey/tests/harness.py

@ -0,0 +1,22 @@
from contextlib import contextmanager
import wx
from gui import application
from python_bindings.config_generator import create_from_parser
from python_bindings.gooey_decorator import defaults
@contextmanager
def instrumentGooey(parser, **kwargs):
"""
Context manager used during testing for setup/tear down of the
WX infrastructure during subTests.
"""
buildspec = create_from_parser(parser, "", **defaults)
app, gooey = application.build_app(buildspec)
try:
yield (app, gooey)
finally:
wx.CallAfter(app.ExitMainLoop)
app.Destroy()

189
gooey/tests/test_radiogroup.py

@ -0,0 +1,189 @@
import unittest
import wx
from gooey import GooeyParser
from tests.harness import instrumentGooey
class TestRadioGroupBehavior(unittest.TestCase):
def mutext_group(self, options):
"""
Basic radio group consisting of two options.
"""
parser = GooeyParser()
group = parser.add_mutually_exclusive_group(**options)
group.add_argument("-b", type=str)
group.add_argument("-d", type=str, widget="DateChooser")
return parser
def test_initial_selection_options(self):
"""
Ensure that the initial_selection GooeyOption behaves as expected.
"""
# each pair in the below datastructure represents input/output
# First position: kwargs which will be supplied to the parser
# Second position: expected indices which buttons/widgets should be enabled/disabled
testCases = [
[{'required': True, 'gooey_options': {}},
{'selected': None, 'enabled': [], 'disabled': [0, 1]}],
# Issue #517 - initial section with required=True was not enabling
# the inner widget
[{'required': True, 'gooey_options': {"initial_selection": 0}},
{'selected': 0, 'enabled': [0], 'disabled': [1]}],
[{'required': True, 'gooey_options': {"initial_selection": 1}},
{'selected': 1, 'enabled': [1], 'disabled': [0]}],
[{'required': False, 'gooey_options': {}},
{'selected': None, 'enabled': [], 'disabled': [0, 1]}],
[{'required': False, 'gooey_options': {"initial_selection": 0}},
{'selected': 0, 'enabled': [0], 'disabled': [1]}],
[{'required': False, 'gooey_options': {"initial_selection": 1}},
{'selected': 1, 'enabled': [1], 'disabled': [0]}],
]
for options, expected in testCases:
parser = self.mutext_group(options)
with self.subTest(options):
with instrumentGooey(parser) as (app, gooeyApp):
radioGroup = gooeyApp.configs[0].reifiedWidgets[0]
# verify that the checkboxes themselves are correct
if expected['selected'] is not None:
self.assertEqual(
radioGroup.selected,
radioGroup.radioButtons[expected['selected']])
else:
self.assertEqual(radioGroup.selected, None)
# verify the widgets contained in the radio group
# are in the correct state
for enabled in expected['enabled']:
# The widget contained within the group should be enabled
self.assertTrue(radioGroup.widgets[enabled].IsEnabled())
# make sure all widgets other than the selected
# are disabled
for enabled in expected['disabled']:
self.assertFalse(radioGroup.widgets[enabled].IsEnabled())
def test_optional_radiogroup_click_behavior(self):
"""
Testing that select/deselect behaves as expected
"""
testcases = [
self.click_scenarios_optional_widget(),
self.click_scenarios_required_widget(),
self.click_scenarios_initial_selection()
]
for testcase in testcases:
with self.subTest(testcase['name']):
# wire up the parse with our test case options
parser = self.mutext_group(testcase['input'])
with instrumentGooey(parser) as (app, gooeyApp):
radioGroup = gooeyApp.configs[0].reifiedWidgets[0]
for scenario in testcase['scenario']:
targetButton = scenario['clickButton']
event = wx.CommandEvent(wx.wxEVT_LEFT_DOWN, wx.NewId())
event.SetEventObject(radioGroup.radioButtons[targetButton])
radioGroup.radioButtons[targetButton].ProcessEvent(event)
expectedEnabled, expectedDisabled = scenario['postState']
for index in expectedEnabled:
self.assertEqual(radioGroup.selected, radioGroup.radioButtons[index])
self.assertTrue(radioGroup.widgets[index].IsEnabled())
for index in expectedDisabled:
self.assertNotEqual(radioGroup.selected, radioGroup.radioButtons[index])
self.assertFalse(radioGroup.widgets[index].IsEnabled())
def click_scenarios_optional_widget(self):
return {
'name': 'click_scenarios_optional_widget',
'input': {'required': False},
'scenario': [
# clicking enabled the button
{'clickButton': 0,
'postState': [[0], [1]]},
# clicking again disables the button (*when not required*)
{'clickButton': 0,
'postState': [[], [0, 1]]},
# clicking group 2 enabled it
{'clickButton': 1,
'postState': [[1], [0]]},
# and similarly clicking group 2 again disables it
{'clickButton': 1,
'postState': [[], [0, 1]]},
# enable second group
{'clickButton': 1,
'postState': [[1], [0]]},
# can switch to group one
{'clickButton': 0,
'postState': [[0], [1]]},
]
}
def click_scenarios_required_widget(self):
return {
'name': 'click_scenarios_required_widget',
'input': {'required': True},
'scenario': [
# clicking enables the button
{'clickButton': 0,
'postState': [[0], [1]]},
# unlike the the optional case, this
# has no effect. You cannot _not_ select something
# when it is required.
{'clickButton': 0,
'postState': [[0], [1]]},
# we can select a different button
{'clickButton': 1,
'postState': [[1], [0]]},
# again, if we click it again, we cannot deselect it
{'clickButton': 1,
'postState': [[1], [0]]},
# we can click back to the other group
{'clickButton': 0,
'postState': [[0], [1]]},
]}
def click_scenarios_initial_selection(self):
return {
'name': 'click_scenarios_initial_selection',
'input': {'required': False, 'gooey_options': {'initial_selection': 0}},
'scenario': [
# we start already selected via GooeyOptions. As such,
# clicking on the radiobutton should deselect it
{'clickButton': 0,
'postState': [[], [0, 1]]},
# clicking again reselected it
{'clickButton': 0,
'postState': [[0], [1]]},
]}
if __name__ == '__main__':
unittest.main()
Loading…
Cancel
Save