@@ -343,11 +343,12 @@ def test_register_argparse_argument_parameter() -> None:
343343
344344
345345def test_parser_attachment () -> None :
346+ """Test the monkey-patched attach_parser and detach_parser methods on argparse._SubParsersAction."""
346347 # Attach a parser as a subcommand
347- root_parser = Cmd2ArgumentParser (description = "root command" )
348+ root_parser = Cmd2ArgumentParser (prog = "root" , description = "root command" )
348349 root_subparsers = root_parser .add_subparsers ()
349350
350- child_parser = Cmd2ArgumentParser (description = "child command" )
351+ child_parser = Cmd2ArgumentParser (prog = "child" , description = "child command" )
351352 root_subparsers .attach_parser ( # type: ignore[attr-defined]
352353 "child" ,
353354 child_parser ,
@@ -385,11 +386,24 @@ def test_parser_attachment() -> None:
385386
386387
387388def test_subcommand_attachment () -> None :
388- # Attach a subcommand
389- root_parser = Cmd2ArgumentParser (description = "root command" )
389+ """Test Cmd2ArgumentParser convenience methods for attaching and detaching subcommands."""
390+
391+ ###############################
392+ # Set up parsers
393+ ###############################
394+ root_parser = Cmd2ArgumentParser (prog = "root" , description = "root command" )
390395 root_subparsers = root_parser .add_subparsers ()
391396
392- child_parser = Cmd2ArgumentParser (description = "child command" )
397+ child_parser = Cmd2ArgumentParser (prog = "child" , description = "child command" )
398+ child_subparsers = child_parser .add_subparsers () # Must have subparsers to host grandchild
399+
400+ grandchild_parser = Cmd2ArgumentParser (prog = "grandchild" , description = "grandchild command" )
401+
402+ ###############################
403+ # Attach subcommands
404+ ###############################
405+
406+ # Attach child to root
393407 root_parser .attach_subcommand (
394408 [],
395409 "child" ,
@@ -398,35 +412,59 @@ def test_subcommand_attachment() -> None:
398412 aliases = ["child_alias" ],
399413 )
400414
401- # Verify the same parser instance was used
415+ # Attach grandchild to child
416+ root_parser .attach_subcommand (
417+ ["child" ],
418+ "grandchild" ,
419+ grandchild_parser ,
420+ help = "a grandchild command" ,
421+ )
422+
423+ ###############################
424+ # Verify hierarchy navigation
425+ ###############################
426+
427+ assert root_parser ._find_parser (["child" , "grandchild" ]) is grandchild_parser
428+ assert root_parser ._find_parser (["child" ]) is child_parser
429+ assert root_parser ._find_parser ([]) is root_parser
430+
431+ ###############################
432+ # Verify attachments
433+ ###############################
434+
435+ # Verify child attachment and aliases
402436 assert root_subparsers ._name_parser_map ["child" ] is child_parser
403437 assert root_subparsers ._name_parser_map ["child_alias" ] is child_parser
404438
405- # Verify an action with the help text exists
406- child_action = None
407- for action in root_subparsers ._choices_actions :
408- if action .dest == "child" :
409- child_action = action
410- break
411- assert child_action is not None
412- assert child_action .help == "a child command"
439+ # Verify grandchild attachment
440+ assert child_subparsers ._name_parser_map ["grandchild" ] is grandchild_parser
413441
414- # Detatch the subcommand
415- detached_parser = root_parser .detach_subcommand ([], "child" )
442+ ###############################
443+ # Detach subcommands
444+ ###############################
416445
417- # Verify subcommand and its aliases were removed
418- assert detached_parser is child_parser
446+ # Detach grandchild from child
447+ detached_grandchild = root_parser .detach_subcommand (["child" ], "grandchild" )
448+ assert detached_grandchild is grandchild_parser
449+ assert "grandchild" not in child_subparsers ._name_parser_map
450+
451+ # Detach child from root
452+ detached_child = root_parser .detach_subcommand ([], "child" )
453+ assert detached_child is child_parser
419454 assert "child" not in root_subparsers ._name_parser_map
420455 assert "child_alias" not in root_subparsers ._name_parser_map
421456
422- # Verify the help text action was removed
423- choices_actions = [ action . dest for action in root_subparsers . _choices_actions ]
424- assert "child" not in choices_actions
457+ ###############################
458+ # Test error handling
459+ ###############################
425460
426- # Verify it raises a ValueError when subcommand does not exist
427- expected_err = "Subcommand 'fake' not found"
428- with pytest .raises (ValueError , match = expected_err ):
429- assert root_parser .detach_subcommand ([], "fake" ) is None
461+ # Verify ValueError when path is invalid (find_parser fails)
462+ with pytest .raises (ValueError , match = "Subcommand 'nonexistent' not found" ):
463+ root_parser .detach_subcommand (["nonexistent" ], "anything" )
464+
465+ # Verify ValueError when path is valid but subcommand name is wrong
466+ with pytest .raises (ValueError , match = "Subcommand 'fake' not found in 'root'" ):
467+ root_parser .detach_subcommand ([], "fake" )
430468
431469
432470def test_completion_items_as_choices (capsys ) -> None :
0 commit comments