import re import sys from io import StringIO from textwrap import dedent from unittest.mock import patch from django.core.management import call_command from django_components import CommandArg, CommandArgGroup, ComponentCommand, ComponentExtension from django_components.testing import djc_test from .testutils import setup_test_config setup_test_config({"autodiscover": False}) # NOTE: Argparse changed how the optional args are displayed in Python 3.11+ if sys.version_info >= (3, 10): OPTIONS_TITLE = "options" else: OPTIONS_TITLE = "optional arguments" class EmptyExtension(ComponentExtension): name = "empty" class DummyCommand(ComponentCommand): name = "dummy_cmd" help = "Dummy command description." arguments = [ CommandArg( name_or_flags="--foo", help="Foo description.", ), CommandArgGroup( title="group bar", description="Group description.", arguments=[ CommandArg( name_or_flags="--bar", help="Bar description.", ), CommandArg( name_or_flags="--baz", help="Baz description.", ), ], ), ] def handle(self, *args, **kwargs): kwargs.pop("_command") kwargs.pop("_parser") sorted_kwargs = dict(sorted(kwargs.items())) print(f"DummyCommand.handle: args={args}, kwargs={sorted_kwargs}") class DummyExtension(ComponentExtension): name = "dummy" commands = [ DummyCommand, ] @djc_test class TestExtensionsCommand: def test_root_command(self): out = StringIO() with patch("sys.stdout", new=out): call_command("components", "ext") output = out.getvalue() assert ( output == dedent( f""" usage: components ext [-h] {{list,run}} ... Run extension commands. {OPTIONS_TITLE}: -h, --help show this help message and exit subcommands: {{list,run}} list List all extensions. run Run a command added by an extension. """ ).lstrip() ) @djc_test class TestExtensionsListCommand: def test_list_default_extensions(self): out = StringIO() with patch("sys.stdout", new=out): call_command("components", "ext", "list") output = out.getvalue() assert output.strip() == ( "name \n" "===============\n" "cache \n" "defaults \n" "dependencies \n" "view \n" "debug_highlight" ) @djc_test( components_settings={"extensions": [EmptyExtension, DummyExtension]}, ) def test_list_extra_extensions(self): out = StringIO() with patch("sys.stdout", new=out): call_command("components", "ext", "list") output = out.getvalue() assert output.strip() == ( "name \n" "===============\n" "cache \n" "defaults \n" "dependencies \n" "view \n" "debug_highlight\n" "empty \n" "dummy" ) @djc_test( components_settings={"extensions": [EmptyExtension, DummyExtension]}, ) def test_list_all(self): out = StringIO() with patch("sys.stdout", new=out): call_command("components", "ext", "list", "--all") output = out.getvalue() assert output.strip() == ( "name \n" "===============\n" "cache \n" "defaults \n" "dependencies \n" "view \n" "debug_highlight\n" "empty \n" "dummy" ) @djc_test( components_settings={"extensions": [EmptyExtension, DummyExtension]}, ) def test_list_specific_columns(self): out = StringIO() with patch("sys.stdout", new=out): call_command("components", "ext", "list", "--columns", "name") output = out.getvalue() assert output.strip() == ( "name \n" "===============\n" "cache \n" "defaults \n" "dependencies \n" "view \n" "debug_highlight\n" "empty \n" "dummy" ) @djc_test( components_settings={"extensions": [EmptyExtension, DummyExtension]}, ) def test_list_simple(self): out = StringIO() with patch("sys.stdout", new=out): call_command("components", "ext", "list", "--simple") output = out.getvalue() assert output.strip() == ( "cache \n" "defaults \n" "dependencies \n" "view \n" "debug_highlight\n" "empty \n" "dummy" ) @djc_test class TestExtensionsRunCommand: @djc_test( components_settings={"extensions": [EmptyExtension, DummyExtension]}, ) def test_run_command_root(self): out = StringIO() with patch("sys.stdout", new=out): call_command("components", "ext", "run") output = out.getvalue() # Fix line breaking in CI on the first line between the `[-h]` and `{{cmd_name}}` output = re.compile(r"\]\s+\{").sub("] {", output) # Fix line breaking in CI on the first line between the `{{cmd_name}}` and `...` output = re.compile(r"\}\s+\.\.\.").sub("} ...", output) assert ( output == dedent( f""" usage: components ext run [-h] {{dummy}} ... Run a command added by an extension. {OPTIONS_TITLE}: -h, --help show this help message and exit subcommands: {{dummy}} dummy Run commands added by the 'dummy' extension. """ ).lstrip() ) @djc_test( components_settings={"extensions": [EmptyExtension, DummyExtension]}, ) def test_run_command_ext_empty(self): out = StringIO() with patch("sys.stdout", new=out): call_command("components", "ext", "run", "dummy") output = out.getvalue() assert ( output == dedent( f""" usage: components ext run dummy [-h] {{dummy_cmd}} ... Run commands added by the 'dummy' extension. {OPTIONS_TITLE}: -h, --help show this help message and exit subcommands: {{dummy_cmd}} dummy_cmd Dummy command description. """ ).lstrip() ) @djc_test( components_settings={"extensions": [EmptyExtension, DummyExtension]}, ) def test_run_command_ext_with_commands(self): out = StringIO() with patch("sys.stdout", new=out): call_command("components", "ext", "run", "dummy") output = out.getvalue() assert ( output == dedent( f""" usage: components ext run dummy [-h] {{dummy_cmd}} ... Run commands added by the 'dummy' extension. {OPTIONS_TITLE}: -h, --help show this help message and exit subcommands: {{dummy_cmd}} dummy_cmd Dummy command description. """ ).lstrip() ) @djc_test( components_settings={"extensions": [EmptyExtension, DummyExtension]}, ) def test_run_command_ext_command(self): out = StringIO() with patch("sys.stdout", new=out): call_command("components", "ext", "run", "dummy", "dummy_cmd") output = out.getvalue() # NOTE: The dummy command prints out the kwargs, which is what we check for here assert ( output == dedent( """ DummyCommand.handle: args=(), kwargs={'bar': None, 'baz': None, 'foo': None, 'force_color': False, 'no_color': False, 'pythonpath': None, 'settings': None, 'skip_checks': True, 'traceback': False, 'verbosity': 1} """ # noqa: E501 ).lstrip() ) @djc_test( components_settings={"extensions": [EmptyExtension, DummyExtension]}, ) def test_prints_error_if_command_not_found(self): out = StringIO() with patch("sys.stderr", new=out): try: call_command("components", "ext", "run", "dummy", "dummy_cmd_not_found") except SystemExit: output = out.getvalue() assert "invalid choice: 'dummy_cmd_not_found'" in output