def testGooeyRequestsConfirmationWhenShowStopWarningModalTrue(self, mockModal): """ When show_stop_warning=False, Gooey should immediately kill the running program without additional user confirmation. Otherwise, Gooey should show a confirmation modal and, dependending on the user's choice, either do nothing or kill the running program. """ Case = namedtuple('Case', ['show_warning', 'shouldSeeConfirm', 'userChooses', 'shouldHaltProgram']) testcases = [ Case(show_warning=True, shouldSeeConfirm=True, userChooses=True, shouldHaltProgram=True), Case(show_warning=True, shouldSeeConfirm=True, userChooses=False, shouldHaltProgram=False), Case(show_warning=False, shouldSeeConfirm=False, userChooses='N/A', shouldHaltProgram=True), ] for case in testcases: mockModal.reset_mock() parser = self.basicParser() with instrumentGooey(parser, show_stop_warning=case.show_warning) as (app, gapp): mockClientRunner = MagicMock() mockModal.return_value = case.userChooses gapp.clientRunner = mockClientRunner gapp.onStopExecution() if case.shouldSeeConfirm: mockModal.assert_called() else: mockModal.assert_not_called() if case.shouldHaltProgram: mockClientRunner.stop.assert_called() else: mockClientRunner.stop.assert_not_called()
def testSliderDefault(self): cases = [ [{}, 0], [{'default': 0}, 0], [{'default': 10}, 10], [{'default': 76}, 76], # note that WX caps the value # unless explicitly widened via gooey_options [{'default': 81234}, 100], # here we set the max to something higher than # the default and all works as expected. # this is a TODO for validation [{'default': 81234, 'gooey_options': {'max': 99999}}, 81234], # Initial Value cases [{}, 0], [{'gooey_options': {'initial_value': 0}}, 0], [{'gooey_options': {'initial_value': 10}}, 10], [{'gooey_options': {'initial_value': 76}}, 76], # note that WX caps the value # unless explicitly widened via gooey_options [{'gooey_options': {'initial_value': 81234}}, 100], # here we set the max to something higher than # the default and all works as expected. # this is a TODO for validation [{'gooey_options': {'initial_value': 81234, 'max': 99999}}, 81234], ] for inputs, expected in cases: with self.subTest(inputs): parser = self.makeParser(**inputs) with instrumentGooey(parser) as (app, frame, gapp): slider = gapp.getActiveConfig().reifiedWidgets[0] self.assertEqual(slider.getValue()['rawValue'], expected)
def testFullscreen(self): parser = self.basicParser() for shouldShow in [True, False]: with self.subTest('Should set full screen: {}'.format(shouldShow)): with instrumentGooey(parser, fullscreen=shouldShow) as (app, gapp): self.assertEqual(gapp.IsFullScreen(), shouldShow)
def testInitialValue(self): widgets = ['ColourChooser', 'CommandField', 'DateChooser', 'DirChooser', 'FileChooser', 'FileSaver', 'FilterableDropdown', 'MultiDirChooser', 'MultiFileChooser', 'PasswordField', 'TextField', 'Textarea', 'TimeChooser'] cases = [ # initial_value supersedes, default Case( {'default': 'default', 'gooey_options': {'initial_value': 'some val'}}, 'some val'), Case( {'gooey_options': {'initial_value': 'some val'}}, 'some val'), Case( {'default': 'default', 'gooey_options': {}}, 'default'), Case({'default': 'default'}, 'default') ] for widgetName in widgets: with self.subTest(widgetName): for case in cases: parser = self.makeParser(widget=widgetName, **case.inputs) with instrumentGooey(parser) as (app, gooeyApp): widget = gooeyApp.configs[0].reifiedWidgets[0] self.assertEqual(widget.getValue()['rawValue'], case.initialExpected)
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, frame, gapp): radioGroup = gapp.getActiveConfig().reifiedWidgets[0] for scenario in testcase['scenario']: targetButton = scenario['clickButton'] event = wx.CommandEvent(wx.wxEVT_LEFT_DOWN, wx.Window.NewControlId()) 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 testDefaultAndInitialValue(self): cases = [ # initial_value takes precedence when both are present Case( { 'default': 'default_val', 'gooey_options': { 'initial_value': 'some val' } }, 'some val', None), # when no default is present # Case({'gooey_options': {'initial_value': 'some val'}}, # 'some val', # ''), # [{'default': 'default', 'gooey_options': {}}, # 'default'], # [{'default': 'default'}, # 'default'], ] for case in cases: parser = self.makeParser(**case.inputs) with instrumentGooey(parser) as (app, frame, gapp): widget = gapp.getActiveConfig().reifiedWidgets[0] self.assertEqual(widget.getValue()['rawValue'], case.initialExpected) widget.setValue('') print(widget.getValue()) self.assertEqual(widget.getValue()['cmd'], case.expectedAfterClearing)
def testInitialValue(self): cases = [ # `initial` should supersede `default` { 'inputs': { 'default': 'b', 'choices': ['a', 'b', 'c'], 'gooey_options': { 'initial_value': 'a' } }, 'expect': ['a'] }, { 'inputs': { 'choices': ['a', 'b', 'c'], 'gooey_options': { 'initial_value': 'a' } }, 'expect': ['a'] }, { 'inputs': { 'choices': ['a', 'b', 'c'], 'gooey_options': { 'initial_value': ['a', 'c'] } }, 'expect': ['a', 'c'] }, { 'inputs': { 'choices': ['a', 'b', 'c'], 'default': 'b', 'gooey_options': {} }, 'expect': ['b'] }, { 'inputs': { 'choices': ['a', 'b', 'c'], 'default': 'b' }, 'expect': ['b'] }, { 'inputs': { 'choices': ['a', 'b', 'c'] }, 'expect': [] } ] for case in cases: with self.subTest(case): parser = self.makeParser(**case['inputs']) with instrumentGooey(parser) as (app, gooeyApp): widget = gooeyApp.configs[0].reifiedWidgets[0] self.assertEqual(widget.getValue()['rawValue'], case['expect'])
def testInitialValue(self): cases = [ # `initial` should supersede `default` {'inputs': {'default': False, 'widget': 'CheckBox', 'gooey_options': {'initial_value': True}}, 'expect': True}, {'inputs': {'gooey_options': {'initial_value': True}, 'widget': 'CheckBox'}, 'expect': True}, {'inputs': {'gooey_options': {'initial_value': False}, 'widget': 'CheckBox'}, 'expect': False}, {'inputs': {'default': True, 'widget': 'CheckBox', 'gooey_options': {}}, 'expect': True}, {'inputs': {'default': True, 'widget': 'CheckBox'}, 'expect': True}, {'inputs': {'widget': 'CheckBox'}, 'expect': False} ] for case in cases: with self.subTest(case): parser = self.makeParser(**case['inputs']) with instrumentGooey(parser) as (app, gooeyApp): widget = gooeyApp.configs[0].reifiedWidgets[0] self.assertEqual(widget.getValue()['rawValue'], case['expect'])
def testFontWeightsGetSet(self): ## Issue #625 font weight wasn't being correctly passed to the terminal for weight in [constants.FONTWEIGHT_LIGHT, constants.FONTWEIGHT_BOLD]: parser = self.basicParser() with instrumentGooey(parser, terminal_font_weight=weight) as (app, gapp): terminal = gapp.console.textbox self.assertEqual(terminal.GetFont().GetWeight(), weight)
def testTerminalColorChanges(self): ## Issue #625 terminal panel color wasn't being set due to a typo parser = self.basicParser() expectedColors = [(255, 0, 0, 255), (255, 255, 255, 255), (100, 100, 100,100)] for expectedColor in expectedColors: with instrumentGooey(parser, terminal_panel_color=expectedColor) as (app, gapp): foundColor = gapp.console.GetBackgroundColour() self.assertEqual(tuple(foundColor), expectedColor)
def testDefault(self): cases = [ [{ 'widget': 'IntegerField' }, 0], [{ 'default': 0, 'widget': 'IntegerField' }, 0], [{ 'default': 10, 'widget': 'IntegerField' }, 10], [{ 'default': 76, 'widget': 'IntegerField' }, 76], # note that WX caps the value # unless explicitly widened via gooey_options [{ 'default': 81234, 'widget': 'IntegerField' }, 100], # here we set the max to something higher than # the default and all works as expected. # this is a TODO for validation [{ 'default': 81234, 'widget': 'IntegerField', 'gooey_options': { 'max': 99999 } }, 81234], [{ 'widget': 'DecimalField' }, 0], [{ 'default': 0, 'widget': 'DecimalField' }, 0], [{ 'default': 81234, 'widget': 'DecimalField' }, 100], [{ 'default': 81234, 'widget': 'DecimalField', 'gooey_options': { 'max': 99999 } }, 81234], ] for inputs, expected in cases: with self.subTest(inputs): parser = self.makeParser(**inputs) with instrumentGooey(parser) as (app, gooeyApp): input = gooeyApp.configs[0].reifiedWidgets[0] self.assertEqual(input.getValue()['rawValue'], expected)
def testNoLossOfPrecision(self): parser = self.makeParser(widget='DecimalField', default=12.23534, gooey_options={'precision': 20}) with instrumentGooey(parser) as (app, gooeyApp): field = gooeyApp.configs[0].reifiedWidgets[0] result = field.getValue() self.assertEqual(result['rawValue'], 12.23534) self.assertIsNotNone(result['cmd'])
def testPlaceholder(self): cases = [[{}, ''], [{'placeholder': 'Hello'}, 'Hello']] for options, expected in cases: parser = self.makeParser(gooey_options=options) with instrumentGooey(parser) as (app, frame, gapp): # because of how poorly designed the Gooey widgets are # we have to reach down 3 levels in order to find the # actual WX object we need to test. widget = gapp.getActiveConfig().reifiedWidgets[0].widget self.assertEqual(widget.widget.GetHint(), expected)
def test_header_string(self): """ Verify that string in the buildspec get correctly placed into the UI. """ parser = ArgumentParser(description='Foobar') with instrumentGooey(parser, program_name='BaZzEr') as (app, gooeyApp): self.assertEqual(gooeyApp.header._header.GetLabelText(), 'BaZzEr') self.assertEqual(gooeyApp.header._subheader.GetLabelText(), 'Foobar')
def testZerosAreReturned(self): """ Originally the formatter was dropping '0' due to it being interpreted as falsey """ parser = self.makeParser() with instrumentGooey(parser) as (app, gooeyApp): field = gooeyApp.configs[0].reifiedWidgets[0] result = field.getValue() self.assertEqual(result['rawValue'], 0) self.assertIsNotNone(result['cmd'])
def test_time_remaining_visibility(self): for testdata in self.testcases(): with self.subTest(testdata): with instrumentGooey(self.make_parser(), timing_options=testdata) as (app, gooeyApp): gooeyApp.showConsole() footer = gooeyApp.footer self.assertEqual( footer.time_remaining_text.Shown, testdata.get('show_time_remaining',False) )
def test_header_string(self): """ Verify that string in the buildspec get correctly placed into the UI. """ parser = ArgumentParser(description='Foobar') with instrumentGooey(parser, program_name='BaZzEr') as (app, frame, gapp): self.assertEqual( frame.FindWindowByName("header_title").GetLabel(), 'BaZzEr') self.assertEqual( frame.FindWindowByName("header_subtitle").GetLabel(), 'Foobar')
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, frame, gapp): radioGroup = gapp.getActiveConfig().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 testOnCloseShutsDownActiveClients(self, mockModal): """ Issue 592: Closing the UI should clean up any actively running programs """ parser = self.basicParser() with instrumentGooey(parser) as (app, gapp): gapp.clientRunner = MagicMock() gapp.destroyGooey = MagicMock() # mocking that the user clicks "yes shut down" in the warning modal mockModal.return_value = True gapp.onClose() mockModal.assert_called() gapp.destroyGooey.assert_called()
def test_time_remaining_visibility(self): for testdata in self.testcases(): with self.subTest(testdata): with instrumentGooey(self.make_parser(), timing_options=testdata) as (app, frame, gapp): gapp.set_state(s.consoleScreen(identity, gapp.state)) app: wx.App = app wx.CallLater(1, app.ExitMainLoop) app.MainLoop() self.assertEqual( frame.FindWindowByName('timing').Shown, testdata.get('show_time_remaining', False))
def test_time_remaining_visibility_on_complete(self): for testdata in self.testcases(): with self.subTest(testdata): with instrumentGooey(self.make_parser(), timing_options=testdata) as (app, gooeyApp): gooeyApp.showComplete() footer = gooeyApp.footer if not testdata.get('show_time_remaining') and testdata: self.assertEqual( footer.time_remaining_text.Shown, testdata.get('hide_time_remaining_on_complete',True) ) else: return True
def testInitialValue(self): cases = [ # `initial` should supersede `default` { 'inputs': { 'default': 1, 'gooey_options': { 'initial_value': 3 } }, 'expect': '3' }, { 'inputs': { 'gooey_options': { 'initial_value': 1 } }, 'expect': '1' }, { 'inputs': { 'default': 2, 'gooey_options': {} }, 'expect': '2' }, { 'inputs': { 'default': 1 }, 'expect': '1' }, { 'inputs': {}, 'expect': None } ] for case in cases: with self.subTest(case): parser = self.makeParser(**case['inputs']) with instrumentGooey(parser) as (app, frame, gapp): widget = gapp.getActiveConfig().reifiedWidgets[0] self.assertEqual(widget.getValue()['rawValue'], case['expect'])
def test_header_visibility(self): """ Test that the title and subtitle components correctly show/hide based on config settings. Verifying Issue #497 """ for testdata in self.testcases(): with self.subTest(testdata): with instrumentGooey(self.make_parser(), **testdata) as (app, gooeyApp): header = gooeyApp.header self.assertEqual(header._header.IsShown(), testdata.get('header_show_title', True)) self.assertEqual( header._subheader.IsShown(), testdata.get('header_show_subtitle', True))
def test_time_remaining_visibility_on_complete(self): for testdata in self.testcases(): with self.subTest(testdata): with instrumentGooey(self.make_parser(), timing_options=testdata) as (app, frame, gapp): gapp.set_state(s.successScreen(identity, gapp.state)) app: wx.App = app wx.CallLater(1, app.ExitMainLoop) app.MainLoop() if not testdata.get('show_time_remaining') and testdata: self.assertEqual( frame.FindWindowByName('timing').Shown, testdata.get('hide_time_remaining_on_complete', True)) else: return True
def test_header_visibility(self): """ Test that the title and subtitle components correctly show/hide based on config settings. Verifying Issue #497 """ for testdata in self.testcases(): with self.subTest(testdata): with instrumentGooey(self.make_parser(), **testdata) as (app, frame, gapp): frame: wx.Frame = frame self.assertEqual( frame.FindWindowByName("header_title").IsShown(), testdata.get('header_show_title', True)) self.assertEqual( frame.FindWindowByName("header_subtitle").IsShown(), testdata.get('header_show_subtitle', True))
def test_dropdown_behavior(self, mock): """ Testing that: - default values are used as the initial selection (when present) - Initial selection defaults to placeholder when no defaults supplied - selection is preserved (when possible) across dynamic updates """ testcases = [ # tuples of [choices, default, initalSelection, dynamicUpdate, expectedFinalSelection] [['1', '2'], None, 'Select Option', ['1', '2', '3'], 'Select Option'], [['1', '2'], '2', '2', ['1', '2', '3'], '2'], [['1', '2'], '1', '1', ['1', '2', '3'], '1'], # dynamic updates removed our selected value; defaults back to placeholder [['1', '2'], '2', '2', ['1', '3'], 'Select Option'], # TODO: this test case is currently passing wrong data for the dynamic # TODO: update due to a bug where Gooey doesn't apply the same ingestion # TODO: rules for data received dynamically as it does for parsers. # TODO: In short, Gooey should be able to handle a list of bools [True, False] # TODO: from dynamics just like it does in parser land. It doesn't currently # TODO: do this, so I'm manually casting it to strings for now. [[True, False], True, 'True', ['True', 'False'], 'True'] ] for choices, default, initalSelection, dynamicUpdate, expectedFinalSelection in testcases: parser = self.makeParser(choices=choices, default=default) with instrumentGooey(parser) as (app, gooeyApp): dropdown = gooeyApp.configs[0].reifiedWidgets[0] # ensure that default values (when supplied) are selected in the UI self.assertEqual(dropdown.widget.GetValue(), initalSelection) # fire a dynamic update with the mock values mock.return_value = {'--dropdown': dynamicUpdate} gooeyApp.fetchExternalUpdates() # the values in the UI now reflect those returned from the update # note: we're appending the ['select option'] bit here as it gets automatically added # in the UI. expectedValues = ['Select Option'] + dynamicUpdate self.assertEqual(dropdown.widget.GetItems(), expectedValues) # and our selection is what we expect self.assertEqual(dropdown.widget.GetValue(), expectedFinalSelection)
def testGooeyOptions(self): cases = [{ 'widget': 'DecimalField', 'gooey_options': { 'min': -100, 'max': 1234, 'increment': 1.240 } }, { 'widget': 'DecimalField', 'gooey_options': { 'min': 1234, 'max': 3456, 'increment': 2.2 } }, { 'widget': 'IntegerField', 'gooey_options': { 'min': -100, 'max': 1234 } }, { 'widget': 'IntegerField', 'gooey_options': { 'min': 1234, 'max': 3456 } }] using = { 'min': lambda widget: widget.GetMin(), 'max': lambda widget: widget.GetMax(), 'increment': lambda widget: widget.GetIncrement(), } for case in cases: with self.subTest(case): parser = self.makeParser(**case) with instrumentGooey(parser) as (app, gooeyApp): wxWidget = gooeyApp.configs[0].reifiedWidgets[0].widget for option, value in case['gooey_options'].items(): self.assertEqual(using[option](wxWidget), value)
def testProgressBarHiddenWhenDisabled(self): options = [{ 'disable_progress_bar_animation': True }, { 'disable_progress_bar_animation': False }, {}] for kwargs in options: parser = self.basicParser() with instrumentGooey(parser, **kwargs) as (app, frame, gapp): mockClientRunner = MagicMock() frame.clientRunner = mockClientRunner # transition's Gooey to the running state using the now mocked processor. # so that we can make assertions about the visibility of footer buttons gapp.onStart() # the progress bar flag is awkwardly inverted (is_disabled, rather than # is_enabled). Thus inverting the expectation here. When disabled is true, # shown should be False, expect_shown = not kwargs.get('disable_progress_bar_animation', False) self.assertEqual(gapp.state['progress']['show'], expect_shown)
def test_validate_form(self): """ Testing the major validation cases we support. """ writer = MagicMock() exit = MagicMock() monkey_patch = control.validate_form(gooey_params(), write=writer, exit=exit) ArgumentParser.original_parse_args = ArgumentParser.parse_args ArgumentParser.parse_args = monkey_patch parser = GooeyParser() # examples: # ERROR: mismatched builtin type parser.add_argument('a', type=int, gooey_options={'initial_value': 'not-an-int'}) # ERROR: mismatched custom type parser.add_argument('b', type=custom_type, gooey_options={'initial_value': 'not-a-float'}) # ERROR: missing required positional arg parser.add_argument('c') # ERROR: missing required 'optional' arg parser.add_argument('--oc', required=True) # VALID: This is one of the bizarre cases which are possible # but don't make much sense. It should pass through as valid # because there's no way for us to send a 'not present optional value' parser.add_argument('--bo', action='store_true', required=True) # ERROR: a required mutex group, with no args supplied. # Should flag all as missing. group = parser.add_mutually_exclusive_group(required=True) group.add_argument('--gp1-a', type=str) group.add_argument('--gp1-b', type=str) # ERROR: required mutex group with a default option but nothing # selected will still fail group2 = parser.add_mutually_exclusive_group(required=True) group2.add_argument('--gp2-a', type=str) group2.add_argument('--gp2-b', type=str, default='Heeeeyyyyy') # VALID: now, same as above, but now the option is actually enabled via # the initial selection. No error. group3 = parser.add_mutually_exclusive_group( required=True, gooey_options={'initial_selection': 1}) group3.add_argument('--gp3-a', type=str) group3.add_argument('--gp3-b', type=str, default='Heeeeyyyyy') # VALID: optional mutex. group4 = parser.add_mutually_exclusive_group() group4.add_argument('--gp4-a', type=str) group4.add_argument('--gp4-b', type=str) # VALID: arg present and type satisfied parser.add_argument('ga', type=str, gooey_options={'initial_value': 'whatever'}) # VALID: arg present and custom type satisfied parser.add_argument('gb', type=custom_type, gooey_options={'initial_value': '1234'}) # VALID: optional parser.add_argument('--gc') # now we're adding the same with instrumentGooey(parser, target='test') as (app, frame, gapp): # we start off with no errors self.assertFalse(s.has_errors(gapp.fullState())) # now we feed our form-validation cmd = s.buildFormValidationCmd(gapp.fullState()) asdf = shlex.split(cmd)[1:] parser.parse_args(shlex.split(cmd)[1:]) assert writer.called assert exit.called result = deserialize_inbound(writer.call_args[0][0].encode('utf-8'), 'utf-8') # Host->Gooey communication is all done over the PublicGooeyState schema # as such, we coarsely validate it's shape here validate_public_state(result) # manually merging the two states back together nextState = s.mergeExternalState(gapp.fullState(), result) # and now we find that we have errors! self.assertTrue(s.has_errors(nextState)) items = s.activeFormState(nextState) self.assertIn('invalid literal', get_by_id(items, 'a')['error']) self.assertIn('KABOOM!', get_by_id(items, 'b')['error']) self.assertIn('required', get_by_id(items, 'c')['error']) self.assertIn('required', get_by_id(items, 'oc')['error']) for item in get_by_id(items, 'group_gp1_a_gp1_b')['options']: self.assertIsNotNone(item['error']) for item in get_by_id(items, 'group_gp2_a_gp2_b')['options']: self.assertIsNotNone(item['error']) for item in get_by_id(items, 'group_gp3_a_gp3_b')['options']: self.assertIsNone(item['error']) # should be None, since this one was entirely optional for item in get_by_id(items, 'group_gp4_a_gp4_b')['options']: self.assertIsNone(item['error']) self.assertIsNone(get_by_id(items, 'bo')['error']) self.assertIsNone(get_by_id(items, 'ga')['error']) self.assertIsNone(get_by_id(items, 'gb')['error']) self.assertIsNone(get_by_id(items, 'gc')['error'])
def test_subparsers(self): """ Making sure that subparsers are handled correctly and all validations still work as expected. """ writer = MagicMock() exit = MagicMock() monkey_patch = control.validate_form(gooey_params(), write=writer, exit=exit) ArgumentParser.original_parse_args = ArgumentParser.parse_args ArgumentParser.parse_args = monkey_patch def build_parser(): # we build a new parser for each subtest # since we monkey patch the hell out of it # each time parser = GooeyParser() subs = parser.add_subparsers() foo = subs.add_parser('foo') foo.add_argument('a') foo.add_argument('b') foo.add_argument('p') bar = subs.add_parser('bar') bar.add_argument('a') bar.add_argument('b') bar.add_argument('z') return parser parser = build_parser() with instrumentGooey(parser, target='test') as (app, frame, gapp): with self.subTest('first subparser'): # we start off with no errors self.assertFalse(s.has_errors(gapp.fullState())) cmd = s.buildFormValidationCmd(gapp.fullState()) parser.parse_args(shlex.split(cmd)[1:]) assert writer.called assert exit.called result = deserialize_inbound( writer.call_args[0][0].encode('utf-8'), 'utf-8') nextState = s.mergeExternalState(gapp.fullState(), result) # by default, the subparser defined first, 'foo', is selected. self.assertIn('foo', nextState['forms']) # and we should find its attributes expected = {'a', 'b', 'p'} actual = {x['id'] for x in nextState['forms']['foo']} self.assertEqual(expected, actual) parser = build_parser() with instrumentGooey(parser, target='test') as (app, frame, gapp): with self.subTest('Second subparser'): # mocking a 'selection change' event to select # the second subparser event = MagicMock() event.Selection = 1 gapp.handleSelectAction(event) # Flushing our events by running the main loop wx.CallLater(1, app.ExitMainLoop) app.MainLoop() cmd = s.buildFormValidationCmd(gapp.fullState()) parser.parse_args(shlex.split(cmd)[1:]) assert writer.called assert exit.called result = deserialize_inbound( writer.call_args[0][0].encode('utf-8'), 'utf-8') nextState = s.mergeExternalState(gapp.fullState(), result) # Now our second subparer, 'bar', should be present. self.assertIn('bar', nextState['forms']) # and we should find its attributes expected = {'a', 'b', 'z'} actual = {x['id'] for x in nextState['forms']['bar']} self.assertEqual(expected, actual)