feat: add a command to list all components (#1041)

* feat: add a command to list all components

* refactor: fix tests

* refactor: fix linter errors

* refactor: fix the tests for tests running within tox

* temp: print out test outputs

* refactor: fix tests for windows

* refactor: remove escape from slash?

* refactor: fixes to regex

* refactor: remove print statements

* docs: update API reference
This commit is contained in:
Juro Oravec 2025-03-19 09:38:25 +01:00 committed by GitHub
parent 107284f474
commit 0f41a62592
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 647 additions and 63 deletions

View file

@ -4,14 +4,9 @@
### <table><td>[Read the full documentation](https://django-components.github.io/django-components/latest/)</td></table>
<!-- TODO - Remove this banner after a month(?), so March 2025 -->
> ⚠️ Attention ⚠️ - We migrated from `EmilStenstrom/django-components` to `django-components/django-components`.
>
> **Repo name and documentation URL changed. Package name remains the same.**
>
> Report any broken links links in [#922](https://github.com/django-components/django-components/issues/922).
`django-components` is a modular and extensible UI framework for Django.
`django-components` combines Django's templating system with the modularity seen
It combines Django's templating system with the modularity seen
in modern frontend frameworks like Vue or React.
With `django-components` you can support Django projects small and large without leaving the Django ecosystem.

View file

@ -387,7 +387,8 @@ python manage.py components ext run my_ext hello
Where:
- `python manage.py components ext run` - is the Django command run
- `python manage.py components` - is the Django entrypoint
- `ext run` - is the subcommand to run extension commands
- `my_ext` - is the extension name
- `hello` - is the command name

View file

@ -9,13 +9,13 @@ that will be added by installing `django_components`:
## ` components`
```txt
usage: python manage.py components [-h] {create,upgrade,ext} ...
usage: python manage.py components [-h] {create,upgrade,ext,list} ...
```
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/commands/components.py#L9" target="_blank">See source code</a>
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/commands/components.py#L10" target="_blank">See source code</a>
@ -34,6 +34,8 @@ The entrypoint for the 'components' commands.
- Upgrade django components syntax from '{% component_block ... %}' to '{% component ... %}'.
- [`ext`](../commands#components-`ext`)
- Run extension commands.
- [`list`](../commands#components-`list`)
- List all components created in this project.
@ -41,6 +43,7 @@ The entrypoint for the 'components' commands.
The entrypoint for the "components" commands.
```bash
python manage.py components list
python manage.py components start <name>
python manage.py components upgrade <name>
python manage.py components ext list
@ -51,9 +54,7 @@ python manage.py components ext run <extension> <command>
## `components create`
```txt
usage: python manage.py components create [-h] [--path PATH] [--js JS] [--css CSS] [--template TEMPLATE] [--force] [--verbose]
[--dry-run]
name
usage: python manage.py components create [-h] [--path PATH] [--js JS] [--css CSS] [--template TEMPLATE] [--force] [--verbose] [--dry-run] name
```
@ -214,7 +215,7 @@ python manage.py components ext run <extension> <command>
## `components ext list`
```txt
usage: python manage.py components ext list [-h] [-v {0,1}]
usage: python manage.py components ext list [-h] [--all] [--columns COLUMNS] [-s]
```
@ -230,8 +231,12 @@ List all extensions.
- `-h`, `--help`
- show this help message and exit
- `-v {0,1}`, `--verbosity {0,1}`
- Verbosity level; 0=minimal output, 1=normal output
- `--all`
- Show all columns. Same as `--columns name`.
- `--columns COLUMNS`
- Comma-separated list of columns to show. Available columns: name. Defaults to `--columns name`.
- `-s`, `--simple`
- Only show table data, without headers. Use this option for generating machine-readable output.
@ -245,16 +250,38 @@ python manage.py components ext list
Prints the list of installed extensions:
```txt
Installed extensions:
name
==============
view
my_extension
```
If you need to omit the title in order to programmatically post-process the output,
you can use the `--verbosity` (or `-v`) flag:
To specify which columns to show, use the `--columns` flag:
```bash
python manage.py components ext list -v 0
python manage.py components ext list --columns name
```
Which prints:
```txt
name
==============
view
my_extension
```
To print out all columns, use the `--all` flag:
```bash
python manage.py components ext list --all
```
If you need to omit the title in order to programmatically post-process the output,
you can use the `--simple` (or `-s`) flag:
```bash
python manage.py components ext list --simple
```
Which prints just:
@ -348,12 +375,94 @@ python manage.py components ext run my_ext hello --name John --shout
For more information, see the [argparse documentation](https://docs.python.org/3/library/argparse.html).
## `components list`
```txt
usage: python manage.py components list [-h] [--all] [--columns COLUMNS] [-s]
```
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/commands/list.py#L136" target="_blank">See source code</a>
List all components created in this project.
**Options:**
- `-h`, `--help`
- show this help message and exit
- `--all`
- Show all columns. Same as `--columns name,full_name,path`.
- `--columns COLUMNS`
- Comma-separated list of columns to show. Available columns: name, full_name, path. Defaults to `--columns full_name,path`.
- `-s`, `--simple`
- Only show table data, without headers. Use this option for generating machine-readable output.
List all components.
```bash
python manage.py components list
```
Prints the list of available components:
```txt
full_name path
==================================================================================================
project.pages.project.ProjectPage ./project/pages/project
project.components.dashboard.ProjectDashboard ./project/components/dashboard
project.components.dashboard_action.ProjectDashboardAction ./project/components/dashboard_action
```
To specify which columns to show, use the `--columns` flag:
```bash
python manage.py components list --columns name,full_name,path
```
Which prints:
```txt
name full_name path
==================================================================================================
ProjectPage project.pages.project.ProjectPage ./project/pages/project
ProjectDashboard project.components.dashboard.ProjectDashboard ./project/components/dashboard
ProjectDashboardAction project.components.dashboard_action.ProjectDashboardAction ./project/components/dashboard_action
```
To print out all columns, use the `--all` flag:
```bash
python manage.py components list --all
```
If you need to omit the title in order to programmatically post-process the output,
you can use the `--simple` (or `-s`) flag:
```bash
python manage.py components list --simple
```
Which prints just:
```txt
ProjectPage project.pages.project.ProjectPage ./project/pages/project
ProjectDashboard project.components.dashboard.ProjectDashboard ./project/components/dashboard
ProjectDashboardAction project.components.dashboard_action.ProjectDashboardAction ./project/components/dashboard_action
```
## `upgradecomponent`
```txt
usage: upgradecomponent [-h] [--path PATH] [--version] [-v {0,1,2,3}] [--settings SETTINGS]
[--pythonpath PYTHONPATH] [--traceback] [--no-color] [--force-color]
[--skip-checks]
[--pythonpath PYTHONPATH] [--traceback] [--no-color] [--force-color] [--skip-checks]
```
@ -397,10 +506,9 @@ Deprecated. Use `components upgrade` instead.
## `startcomponent`
```txt
usage: startcomponent [-h] [--path PATH] [--js JS] [--css CSS] [--template TEMPLATE] [--force]
[--verbose] [--dry-run] [--version] [-v {0,1,2,3}] [--settings SETTINGS]
[--pythonpath PYTHONPATH] [--traceback] [--no-color] [--force-color]
[--skip-checks]
usage: startcomponent [-h] [--path PATH] [--js JS] [--css CSS] [--template TEMPLATE] [--force] [--verbose]
[--dry-run] [--version] [-v {0,1,2,3}] [--settings SETTINGS] [--pythonpath PYTHONPATH]
[--traceback] [--no-color] [--force-color] [--skip-checks]
name
```

View file

@ -88,6 +88,7 @@ exclude = [
'build',
]
per-file-ignores = [
'tests/test_command_list.py:E501',
'tests/test_component_media.py:E501',
'tests/test_dependency_rendering.py:E501',
]

View file

@ -1,5 +1,6 @@
from django_components.commands.create import CreateCommand
from django_components.commands.ext import ExtCommand
from django_components.commands.list import ComponentListCommand
from django_components.commands.upgrade import UpgradeCommand
from django_components.util.command import ComponentCommand
@ -12,6 +13,7 @@ class ComponentsRootCommand(ComponentCommand):
The entrypoint for the "components" commands.
```bash
python manage.py components list
python manage.py components start <name>
python manage.py components upgrade <name>
python manage.py components ext list
@ -26,4 +28,5 @@ class ComponentsRootCommand(ComponentCommand):
CreateCommand,
UpgradeCommand,
ExtCommand,
ComponentListCommand,
]

View file

@ -1,10 +1,10 @@
from typing import Any
from typing import Any, Dict, List
from django_components.commands.list import ListCommand
from django_components.extension import extensions
from django_components.util.command import CommandArg, ComponentCommand
class ExtListCommand(ComponentCommand):
class ExtListCommand(ListCommand):
"""
List all extensions.
@ -15,16 +15,38 @@ class ExtListCommand(ComponentCommand):
Prints the list of installed extensions:
```txt
Installed extensions:
name
==============
view
my_extension
```
If you need to omit the title in order to programmatically post-process the output,
you can use the `--verbosity` (or `-v`) flag:
To specify which columns to show, use the `--columns` flag:
```bash
python manage.py components ext list -v 0
python manage.py components ext list --columns name
```
Which prints:
```txt
name
==============
view
my_extension
```
To print out all columns, use the `--all` flag:
```bash
python manage.py components ext list --all
```
If you need to omit the title in order to programmatically post-process the output,
you can use the `--simple` (or `-s`) flag:
```bash
python manage.py components ext list --simple
```
Which prints just:
@ -38,18 +60,15 @@ class ExtListCommand(ComponentCommand):
name = "list"
help = "List all extensions."
arguments = [
CommandArg(
["-v", "--verbosity"],
default=1,
type=int,
choices=[0, 1],
help=("Verbosity level; 0=minimal output, 1=normal output"),
),
]
columns = ["name"]
default_columns = ["name"]
def handle(self, *args: Any, **kwargs: Any) -> None:
if kwargs["verbosity"] > 0:
print("Installed extensions:")
def get_data(self) -> List[Dict[str, Any]]:
data: List[Dict[str, Any]] = []
for extension in extensions.extensions:
print(extension.name)
data.append(
{
"name": extension.name,
}
)
return data

View file

@ -40,7 +40,7 @@ def _gen_subcommands() -> List[Type[ComponentCommand]]:
#
# NOTE: This is possible, because Django sets up the project and settings BEFORE the commands are loaded.
class SubcommandsDescriptor:
def __get__(self, obj: Any, objtype: Optional[Type] = None) -> List[Type[ComponentCommand]]:
def __get__(self, obj: Optional[Any], objtype: Type) -> List[Type[ComponentCommand]]:
# This will be called when accessing ExtRunCommand.subcommands
# or instance.subcommands
return _gen_subcommands()

View file

@ -0,0 +1,222 @@
import os
from typing import Any, Dict, List, Optional, Type
from django_components.component import all_components
from django_components.util.command import CommandArg, ComponentCommand
from django_components.util.misc import get_import_path, get_module_info
# This descriptor generates the list of command arguments (e.g. `--all`), such that
# their descriptions are dynamically generated based on the Command class.
class ListArgumentsDescriptor:
# This will be called when accessing `ListCommand.arguments`
def __get__(self, obj: Optional["ListCommand"], cls: Type["ListCommand"]) -> List[CommandArg]:
command = obj or cls
all_cols = command.columns
default_cols = command.default_columns
all_cols_input = ",".join(all_cols)
all_cols_readable = ", ".join(all_cols)
default_cols_input = ",".join(default_cols)
return [
CommandArg(
["--all"],
action="store_true",
# Format as e.g. `--columns name,full_name,path`
help=f"Show all columns. Same as `--columns {all_cols_input}`.",
),
CommandArg(
["--columns"],
help=(
f"Comma-separated list of columns to show. Available columns: {all_cols_readable}. "
f"Defaults to `--columns {default_cols_input}`."
),
),
CommandArg(
["-s", "--simple"],
action="store_true",
help="Only show table data, without headers. Use this option for generating machine-readable output.",
),
]
# Common base class for all `list` commands, so they all have the same arguments
# and same formatting.
class ListCommand(ComponentCommand):
####################
# SUBCLASS API
####################
columns: List[str]
default_columns: List[str]
def get_data(self) -> List[Dict[str, Any]]:
return []
####################
# INTERNAL
####################
arguments = ListArgumentsDescriptor() # type: ignore[assignment]
def handle(self, *args: Any, **kwargs: Any) -> None:
"""
This runs when the "list" command is called. This handler delegates to subclasses
to define how to get the data with the `get_data` method and formats the results
as an ASCII table.
"""
if kwargs["all"] and kwargs["columns"]:
raise ValueError("Cannot use --all and --columns together.")
if kwargs["all"]:
columns = self.columns
elif kwargs["columns"]:
columns = kwargs["columns"].split(",")
for column in columns:
if column not in self.columns:
raise ValueError(f"Invalid column: {column}")
else:
# Default columns
columns = self.default_columns
data = self.get_data()
include_headers = not kwargs.get("simple", False)
table = format_as_ascii_table(data, columns, include_headers=include_headers)
print(table)
def format_as_ascii_table(data: List[Dict[str, Any]], headers: List[str], include_headers: bool = True) -> str:
"""
Format a list of dictionaries as an ASCII table.
Example:
```python
data = [
{"name": "ProjectPage", "full_name": "project.pages.project.ProjectPage", "path": "./project/pages/project"},
{"name": "ProjectDashboard", "full_name": "project.components.dashboard.ProjectDashboard", "path": "./project/components/dashboard"},
{"name": "ProjectDashboardAction", "full_name": "project.components.dashboard_action.ProjectDashboardAction", "path": "./project/components/dashboard_action"},
]
headers = ["name", "full_name", "path"]
print(format_as_ascii_table(data, headers))
```
Which prints:
```txt
name full_name path
==================================================================================================
ProjectPage project.pages.project.ProjectPage ./project/pages/project
ProjectDashboard project.components.dashboard.ProjectDashboard ./project/components/dashboard
ProjectDashboardAction project.components.dashboard_action.ProjectDashboardAction ./project/components/dashboard_action
```
""" # noqa: E501
# Calculate the width of each column
column_widths = {header: len(header) for header in headers}
for row in data:
for header in headers:
row_value = str(row.get(header, ""))
column_widths[header] = max(column_widths[header], len(row_value))
# Create the header row
header_row = " ".join(f"{header:<{column_widths[header]}}" for header in headers)
separator = "=" * len(header_row)
# Create the data rows
data_rows = []
for row in data:
row_values = [str(row.get(header, "")) for header in headers]
data_row = " ".join(f"{value:<{column_widths[header]}}" for value, header in zip(row_values, headers))
data_rows.append(data_row)
# Combine all parts into the final table
if include_headers:
table = "\n".join([header_row, separator] + data_rows)
else:
table = "\n".join(data_rows)
return table
class ComponentListCommand(ListCommand):
"""
List all components.
```bash
python manage.py components list
```
Prints the list of available components:
```txt
full_name path
==================================================================================================
project.pages.project.ProjectPage ./project/pages/project
project.components.dashboard.ProjectDashboard ./project/components/dashboard
project.components.dashboard_action.ProjectDashboardAction ./project/components/dashboard_action
```
To specify which columns to show, use the `--columns` flag:
```bash
python manage.py components list --columns name,full_name,path
```
Which prints:
```txt
name full_name path
==================================================================================================
ProjectPage project.pages.project.ProjectPage ./project/pages/project
ProjectDashboard project.components.dashboard.ProjectDashboard ./project/components/dashboard
ProjectDashboardAction project.components.dashboard_action.ProjectDashboardAction ./project/components/dashboard_action
```
To print out all columns, use the `--all` flag:
```bash
python manage.py components list --all
```
If you need to omit the title in order to programmatically post-process the output,
you can use the `--simple` (or `-s`) flag:
```bash
python manage.py components list --simple
```
Which prints just:
```txt
ProjectPage project.pages.project.ProjectPage ./project/pages/project
ProjectDashboard project.components.dashboard.ProjectDashboard ./project/components/dashboard
ProjectDashboardAction project.components.dashboard_action.ProjectDashboardAction ./project/components/dashboard_action
```
""" # noqa: E501
name = "list"
help = "List all components created in this project."
columns = ["name", "full_name", "path"]
default_columns = ["full_name", "path"]
def get_data(self) -> List[Dict[str, Any]]:
components = all_components()
data: List[Dict[str, Any]] = []
for component in components:
full_name = get_import_path(component)
module, module_name, module_file_path = get_module_info(component)
# Make paths relative to CWD
if module_file_path:
module_file_path = os.path.relpath(module_file_path, os.getcwd())
data.append(
{
"name": component.__name__,
"full_name": full_name,
"path": module_file_path,
}
)
return data

View file

@ -1,6 +1,9 @@
import re
import sys
from hashlib import md5
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Type, TypeVar
from importlib import import_module
from types import ModuleType
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Tuple, Type, TypeVar, Union
from django_components.util.nanoid import generate
@ -59,6 +62,31 @@ def get_import_path(cls_or_fn: Type[Any]) -> str:
return module + "." + cls_or_fn.__qualname__
def get_module_info(
cls_or_fn: Union[Type[Any], Callable[..., Any]],
) -> Tuple[Optional[ModuleType], Optional[str], Optional[str]]:
"""Get the module, module name and module file path where the class or function is defined."""
module_name: Optional[str] = getattr(cls_or_fn, "__module__", None)
if module_name:
if module_name in sys.modules:
module = sys.modules[module_name]
else:
try:
module = import_module(module_name)
except Exception:
module = None
else:
module = None
if module:
module_file_path: Optional[str] = getattr(module, "__file__", None)
else:
module_file_path = None
return module, module_name, module_file_path
def default(val: Optional[T], default: T) -> T:
return val if val is not None else default

View file

@ -91,40 +91,57 @@ class TestExtensionsCommand:
@djc_test
class TestExtensionsListCommand:
def test_list_command_default(self):
def test_list_default_extensions(self):
out = StringIO()
with patch("sys.stdout", new=out):
call_command("components", "ext", "list")
output = out.getvalue()
assert "Installed extensions:\nview\n" == output
# Check that it omits the title when verbose is 0
out = StringIO()
with patch("sys.stdout", new=out):
call_command("components", "ext", "list", "-v", "0")
output = out.getvalue()
assert "view\n" == output
assert output.strip() == "name\n====\nview"
@djc_test(
components_settings={"extensions": [EmptyExtension, DummyExtension]},
)
def test_list_command_extra(self):
def test_list_extra_extensions(self):
out = StringIO()
with patch("sys.stdout", new=out):
call_command("components", "ext", "list")
output = out.getvalue()
assert "Installed extensions:\nview\nempty\ndummy\n" == output
assert output.strip() == "name \n=====\nview \nempty\ndummy"
# Check that it omits the title when verbose is 0
@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", "-v", "0")
call_command("components", "ext", "list", "--all")
output = out.getvalue()
assert "view\nempty\ndummy\n" == output
assert output.strip() == "name \n=====\nview \nempty\ndummy"
@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=====\nview \nempty\ndummy"
@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() == "view \nempty\ndummy"
@djc_test

190
tests/test_command_list.py Normal file
View file

@ -0,0 +1,190 @@
import re
from io import StringIO
from unittest.mock import patch
from django.core.management import call_command
from django_components import Component
from django_components.testing import djc_test
from .testutils import setup_test_config
setup_test_config({"autodiscover": False})
# Either back or forward slash
SLASH = r"[\\/]"
@djc_test
class TestComponentListCommand:
def test_list_default(self):
class TestComponent(Component):
template = ""
out = StringIO()
with patch("sys.stdout", new=out):
call_command("components", "list")
output = out.getvalue()
# NOTE: When we run all tests, the output is different, as other test files define components
# outside of the `@djc_test` decorator, and thus they leak into the output. Since this affects also
# the formatting (how much whitespace there is), regex is used to check for the headers and the expected
# components.
#
# The output should look like this:
#
# full_name path
# ======================================================================================================================================
# django_components.components.dynamic.DynamicComponent src/django_components/components/dynamic.py
# tests.test_command_list.TestComponentListCommand.test_list_default.<locals>.TestComponent tests/test_command_list.py
# Check first line of output
assert re.compile(
# full_name path
r"full_name\s+path\s+"
).search(output.strip().split("\n")[0])
# Check that the output contains the built-in component
assert re.compile(
# django_components.components.dynamic.DynamicComponent src/django_components/components/dynamic.py
# or
# django_components.components.dynamic.DynamicComponent .tox/py311/lib/python3.11/site-packages/django_components/components/dynamic.py
r"django_components\.components\.dynamic\.DynamicComponent\s+[\w/\\.-]+django_components{SLASH}components{SLASH}dynamic\.py".format(
SLASH=SLASH
)
).search(output)
# Check that the output contains the test component
assert re.compile(
# tests.test_command_list.TestComponentListCommand.test_list_default.<locals>.TestComponent tests/test_command_list.py
r"tests\.test_command_list\.TestComponentListCommand\.test_list_default\.<locals>\.TestComponent\s+tests{SLASH}test_command_list\.py".format(
SLASH=SLASH
)
).search(output)
def test_list_all(self):
class TestComponent(Component):
template = ""
out = StringIO()
with patch("sys.stdout", new=out):
call_command("components", "list", "--all")
output = out.getvalue()
# NOTE: When we run all tests, the output is different, as other test files define components
# outside of the `@djc_test` decorator, and thus they leak into the output. Since this affects also
# the formatting (how much whitespace there is), regex is used to check for the headers and the expected
# components.
#
# The output should look like this:
#
# name full_name path
# ====================================================================================================================================================
# DynamicComponent django_components.components.dynamic.DynamicComponent src/django_components/components/dynamic.py
# TestComponent tests.test_command_list.TestComponentListCommand.test_list_all.<locals>.TestComponent tests/test_command_list.py
# Check first line of output
assert re.compile(
# name full_name path
r"name\s+full_name\s+path\s+"
).search(output.strip().split("\n")[0])
# Check that the output contains the built-in component
assert re.compile(
# DynamicComponent django_components.components.dynamic.DynamicComponent src/django_components/components/dynamic.py
# or
# DynamicComponent django_components.components.dynamic.DynamicComponent .tox/py311/lib/python3.11/site-packages/django_components/components/dynamic.py
r"DynamicComponent\s+django_components\.components\.dynamic\.DynamicComponent\s+[\w/\\.-]+django_components{SLASH}components{SLASH}dynamic\.py".format(
SLASH=SLASH
)
).search(output)
# Check that the output contains the test component
assert re.compile(
# TestComponent tests.test_command_list.TestComponentListCommand.test_list_all.<locals>.TestComponent tests/test_command_list.py
r"TestComponent\s+tests\.test_command_list\.TestComponentListCommand\.test_list_all\.<locals>\.TestComponent\s+tests{SLASH}test_command_list\.py".format(
SLASH=SLASH
)
).search(output)
def test_list_specific_columns(self):
class TestComponent(Component):
template = ""
out = StringIO()
with patch("sys.stdout", new=out):
call_command("components", "list", "--columns", "name,full_name")
output = out.getvalue()
# NOTE: When we run all tests, the output is different, as other test files define components
# outside of the `@djc_test` decorator, and thus they leak into the output. Since this affects also
# the formatting (how much whitespace there is), regex is used to check for the headers and the expected
# components.
#
# The output should look like this:
#
# name full_name
# ====================================================================================================================
# DynamicComponent django_components.components.dynamic.DynamicComponent
# TestComponent tests.test_command_list.TestComponentListCommand.test_list_specific_columns.<locals>.TestComponent
# Check first line of output
assert re.compile(
# name full_name
r"name\s+full_name"
).search(output.strip().split("\n")[0])
# Check that the output contains the built-in component
assert re.compile(
# DynamicComponent django_components.components.dynamic.DynamicComponent
r"DynamicComponent\s+django_components\.components\.dynamic\.DynamicComponent"
).search(output)
# Check that the output contains the test component
assert re.compile(
# TestComponent tests.test_command_list.TestComponentListCommand.test_list_specific_columns.<locals>.TestComponent
r"TestComponent\s+tests\.test_command_list\.TestComponentListCommand\.test_list_specific_columns\.<locals>\.TestComponent"
).search(output)
def test_list_simple(self):
class TestComponent(Component):
template = ""
out = StringIO()
with patch("sys.stdout", new=out):
call_command("components", "list", "--simple")
output = out.getvalue()
# NOTE: When we run all tests, the output is different, as other test files define components
# outside of the `@djc_test` decorator, and thus they leak into the output. Since this affects also
# the formatting (how much whitespace there is), regex is used to check for the headers and the expected
# components.
#
# The output should look like this:
#
# django_components.components.dynamic.DynamicComponent src/django_components/components/dynamic.py
# tests.test_command_list.TestComponentListCommand.test_list_simple.<locals>.TestComponent tests/test_command_list.py
# Check first line of output is omitted
assert re.compile(
# full_name path
r"full_name\s+path\s+"
).search(output.strip().split("\n")[0]) is None
# Check that the output contains the built-in component
assert re.compile(
# django_components.components.dynamic.DynamicComponent src/django_components/components/dynamic.py
# or
# django_components.components.dynamic.DynamicComponent .tox/py311/lib/python3.11/site-packages/django_components/components/dynamic.py
r"django_components\.components\.dynamic\.DynamicComponent\s+[\w/\\.-]+django_components{SLASH}components{SLASH}dynamic\.py".format(
SLASH=SLASH
)
).search(output)
# Check that the output contains the test component
assert re.compile(
# tests.test_command_list.TestComponentListCommand.test_list_simple.<locals>.TestComponent tests/test_command_list.py
r"tests\.test_command_list\.TestComponentListCommand\.test_list_simple\.<locals>\.TestComponent\s+tests{SLASH}test_command_list\.py".format(
SLASH=SLASH
)
).search(output)