feat: allow globs when specifynigg additionall JS and CSS (#1043)

* feat: allow globs when specifynigg additionall JS and CSS

* refactor: fix tests and linter errors
This commit is contained in:
Juro Oravec 2025-03-21 10:23:38 +01:00 committed by GitHub
parent 73e94b6714
commit ab75cfdb8f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 282 additions and 97 deletions

View file

@ -1,5 +1,19 @@
# Release notes # Release notes
## v0.132
#### Feat
- Allow to use glob patterns as paths for additional JS / CSS in
`Component.Media.js` and `Component.Media.css`
```py
class MyComponent(Component):
class Media:
js = ["*.js"]
css = ["*.css"]
```
## v0.131 ## v0.131
#### Feat #### Feat

View file

@ -143,17 +143,22 @@ However, there's a few differences from Django's Media class:
2. Individual JS / CSS files can be any of `str`, `bytes`, `Path`, 2. Individual JS / CSS files can be any of `str`, `bytes`, `Path`,
[`SafeString`](https://docs.djangoproject.com/en/5.1/ref/utils/#django.utils.safestring.SafeString), or a function [`SafeString`](https://docs.djangoproject.com/en/5.1/ref/utils/#django.utils.safestring.SafeString), or a function
(See [`ComponentMediaInputPath`](../../../reference/api#django_components.ComponentMediaInputPath)). (See [`ComponentMediaInputPath`](../../../reference/api#django_components.ComponentMediaInputPath)).
3. Individual JS / CSS files can be glob patterns, e.g. `*.js` or `styles/**/*.css`.
4. If you set [`Media.extend`](../../../reference/api/#django_components.ComponentMediaInput.extend) to a list,
it should be a list of [`Component`](../../../reference/api/#django_components.Component) classes.
```py ```py
class MyTable(Component): class MyTable(Component):
class Media: class Media:
js = [ js = [
"path/to/script.js", "path/to/script.js",
"path/to/*.js", # Or as a glob
"https://unpkg.com/alpinejs@3.14.7/dist/cdn.min.js", # AlpineJS "https://unpkg.com/alpinejs@3.14.7/dist/cdn.min.js", # AlpineJS
] ]
css = { css = {
"all": [ "all": [
"path/to/style.css", "path/to/style.css",
"path/to/*.css", # Or as a glob
"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", # TailwindCSS "https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", # TailwindCSS
], ],
"print": ["path/to/style2.css"], "print": ["path/to/style2.css"],

View file

@ -230,7 +230,9 @@ with a few differences:
1. Our Media class accepts various formats for the JS and CSS files: either a single file, a list, or (CSS-only) a dictonary (see below). 1. Our Media class accepts various formats for the JS and CSS files: either a single file, a list, or (CSS-only) a dictonary (see below).
2. Individual JS / CSS files can be any of `str`, `bytes`, `Path`, [`SafeString`](https://dev.to/doridoro/django-safestring-afj), or a function. 2. Individual JS / CSS files can be any of `str`, `bytes`, `Path`, [`SafeString`](https://dev.to/doridoro/django-safestring-afj), or a function.
3. If you set `Media.extend` to a list, it should be a list of `Component` classes. 3. Individual JS / CSS files can be glob patterns, e.g. `*.js` or `styles/**/*.css`.
4. If you set [`Media.extend`](../../reference/api/#django_components.ComponentMediaInput.extend) to a list,
it should be a list of [`Component`](../../reference/api/#django_components.Component) classes.
[Learn more](../../concepts/fundamentals/defining_js_css_html_files) about using Media. [Learn more](../../concepts/fundamentals/defining_js_css_html_files) about using Media.
@ -245,10 +247,12 @@ class Calendar(Component):
class Media: # <--- new class Media: # <--- new
js = [ js = [
"path/to/shared.js", "path/to/shared.js",
"path/to/*.js", # Or as a glob
"https://unpkg.com/alpinejs@3.14.7/dist/cdn.min.js", # AlpineJS "https://unpkg.com/alpinejs@3.14.7/dist/cdn.min.js", # AlpineJS
] ]
css = [ css = [
"path/to/shared.css", "path/to/shared.css",
"path/to/*.css", # Or as a glob
"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", # Tailwind "https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", # Tailwind
] ]

View file

@ -1,10 +1,26 @@
import glob
import os import os
import sys import sys
from collections import deque from collections import deque
from copy import copy from copy import copy
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Literal, Optional, Protocol, Tuple, Type, Union, cast from typing import (
TYPE_CHECKING,
Any,
Callable,
Dict,
Iterable,
List,
Literal,
Optional,
Protocol,
Sequence,
Tuple,
Type,
Union,
cast,
)
from weakref import WeakKeyDictionary from weakref import WeakKeyDictionary
from django.contrib.staticfiles import finders from django.contrib.staticfiles import finders
@ -16,7 +32,7 @@ from django.utils.safestring import SafeData
from django_components.util.loader import get_component_dirs, resolve_file from django_components.util.loader import get_component_dirs, resolve_file
from django_components.util.logger import logger from django_components.util.logger import logger
from django_components.util.misc import get_import_path from django_components.util.misc import flatten, get_import_path, get_module_info, is_glob
if TYPE_CHECKING: if TYPE_CHECKING:
from django_components.component import Component from django_components.component import Component
@ -649,19 +665,19 @@ def _normalize_media(media: Type[ComponentMediaInput]) -> None:
_map_media_filepaths(media, _normalize_media_filepath) _map_media_filepaths(media, _normalize_media_filepath)
def _map_media_filepaths(media: Type[ComponentMediaInput], map_fn: Callable[[Any], Any]) -> None: def _map_media_filepaths(media: Type[ComponentMediaInput], map_fn: Callable[[Sequence[Any]], Iterable[Any]]) -> None:
if hasattr(media, "css") and media.css: if hasattr(media, "css") and media.css:
if not isinstance(media.css, dict): if not isinstance(media.css, dict):
raise ValueError(f"Media.css must be a dict, got {type(media.css)}") raise ValueError(f"Media.css must be a dict, got {type(media.css)}")
for media_type, path_list in media.css.items(): for media_type, path_list in media.css.items():
media.css[media_type] = list(map(map_fn, path_list)) # type: ignore[assignment] media.css[media_type] = list(map_fn(path_list)) # type: ignore[assignment]
if hasattr(media, "js") and media.js: if hasattr(media, "js") and media.js:
if not isinstance(media.js, (list, tuple)): if not isinstance(media.js, (list, tuple)):
raise ValueError(f"Media.css must be a list, got {type(media.css)}") raise ValueError(f"Media.css must be a list, got {type(media.css)}")
media.js = list(map(map_fn, media.js)) media.js = list(map_fn(media.js))
def _is_media_filepath(filepath: Any) -> bool: def _is_media_filepath(filepath: Any) -> bool:
@ -683,26 +699,33 @@ def _is_media_filepath(filepath: Any) -> bool:
return False return False
def _normalize_media_filepath(filepath: ComponentMediaInputPath) -> Union[str, SafeData]: def _normalize_media_filepath(filepaths: Sequence[ComponentMediaInputPath]) -> List[Union[str, SafeData]]:
if callable(filepath): normalized: List[Union[str, SafeData]] = []
filepath = filepath() for filepath in filepaths:
if callable(filepath):
filepath = filepath()
if isinstance(filepath, SafeData) or hasattr(filepath, "__html__"): if isinstance(filepath, SafeData) or hasattr(filepath, "__html__"):
return filepath normalized.append(filepath)
continue
if isinstance(filepath, (Path, os.PathLike)) or hasattr(filepath, "__fspath__"): if isinstance(filepath, (Path, os.PathLike)) or hasattr(filepath, "__fspath__"):
# In case of Windows OS, convert to forward slashes # In case of Windows OS, convert to forward slashes
filepath = Path(filepath.__fspath__()).as_posix() filepath = Path(filepath.__fspath__()).as_posix()
if isinstance(filepath, bytes): if isinstance(filepath, bytes):
filepath = filepath.decode("utf-8") filepath = filepath.decode("utf-8")
if isinstance(filepath, str): if isinstance(filepath, str):
return filepath normalized.append(filepath)
continue
raise ValueError( raise ValueError(
"Unknown filepath. Must be str, bytes, PathLike, SafeString, or a function that returns one of the former" f"Unknown filepath {filepath} of type {type(filepath)}. Must be str, bytes, PathLike, SafeString,"
) " or a function that returns one of the former"
)
return normalized
def _resolve_component_relative_files( def _resolve_component_relative_files(
@ -730,12 +753,9 @@ def _resolve_component_relative_files(
return return
component_name = comp_cls.__qualname__ component_name = comp_cls.__qualname__
# Derive the full path of the file where the component was defined # Get the full path of the file where the component was defined
module_name = comp_cls.__module__ module, module_name, module_file_path = get_module_info(comp_cls)
module_obj = sys.modules[module_name] if not module_file_path:
file_path = module_obj.__file__
if not file_path:
logger.debug( logger.debug(
f"Could not resolve the path to the file for component '{component_name}'." f"Could not resolve the path to the file for component '{component_name}'."
" Paths for HTML, JS or CSS templates will NOT be resolved relative to the component file." " Paths for HTML, JS or CSS templates will NOT be resolved relative to the component file."
@ -743,83 +763,147 @@ def _resolve_component_relative_files(
return return
# Get the directory where the component class is defined # Get the directory where the component class is defined
try: matched_component_dir = _find_component_dir_containing_file(comp_dirs, module_file_path)
comp_dir_abs, comp_dir_rel = _get_dir_path_from_component_path(file_path, comp_dirs)
except RuntimeError: # If no dir was found (e.g. the component was defined at runtime), we assume that the media paths
# If no dir was found, we assume that the path is NOT relative to the component dir # are NOT relative.
if matched_component_dir is None:
logger.debug( logger.debug(
f"No component directory found for component '{component_name}' in {file_path}" f"No component directory found for component '{component_name}' in {module_file_path}"
" If this component defines HTML, JS or CSS templates relatively to the component file," " If this component defines HTML, JS or CSS templates relatively to the component file,"
" then check that the component's directory is accessible from one of the paths" " then check that the component's directory is accessible from one of the paths"
" specified in the Django's 'COMPONENTS.dirs' settings." " specified in the Django's 'COMPONENTS.dirs' settings."
) )
return return
matched_component_dir_abs = os.path.abspath(matched_component_dir)
# Derive the path from matched `COMPONENTS.dirs` to the dir where the current component file is.
component_module_dir_path_abs = os.path.dirname(module_file_path)
# Check if filepath refers to a file that's in the same directory as the component class. # Check if filepath refers to a file that's in the same directory as the component class.
# If yes, modify the path to refer to the relative file. # If yes, modify the path to refer to the relative file.
# If not, don't modify anything. # If not, don't modify anything.
def resolve_media_file(filepath: Union[str, SafeData]) -> Union[str, SafeData]: def resolve_relative_media_file(
if isinstance(filepath, str): filepath: Union[str, SafeData],
filepath_abs = os.path.join(comp_dir_abs, filepath) allow_glob: bool,
# NOTE: The paths to resources need to use POSIX (forward slashes) for Django to wor ) -> List[Union[str, SafeData]]:
# See https://github.com/django-components/django-components/issues/796 resolved_filepaths, has_matched = resolve_media_file(
filepath_rel_to_comp_dir = Path(os.path.join(comp_dir_rel, filepath)).as_posix() filepath,
allow_glob,
if os.path.isfile(filepath_abs): static_files_dir=matched_component_dir_abs,
# NOTE: It's important to use `repr`, so we don't trigger __str__ on SafeStrings media_root_dir=component_module_dir_path_abs,
logger.debug(
f"Interpreting template '{repr(filepath)}' of component '{module_name}'"
" relatively to component file"
)
return filepath_rel_to_comp_dir
# If resolved absolute path does NOT exist or filepath is NOT a string, then return as is
logger.debug(
f"Interpreting template '{repr(filepath)}' of component '{module_name}'"
" relatively to components directory"
) )
return filepath
# NOTE: It's important to use `repr`, so we don't trigger __str__ on SafeStrings
if has_matched:
logger.debug(
f"Interpreting file '{repr(filepath)}' of component '{module_name}'" " relatively to component file"
)
else:
logger.debug(
f"Interpreting file '{repr(filepath)}' of component '{module_name}'"
" relatively to components directory"
)
return resolved_filepaths
# Check if filepath is a glob pattern that points to files relative to the components directory
# (as defined by `COMPONENTS.dirs` and `COMPONENTS.app_dirs` settings) in which the component is defined.
# If yes, modify the path to refer to the globbed files.
# If not, don't modify anything.
def resolve_static_media_file(
filepath: Union[str, SafeData],
allow_glob: bool,
) -> List[Union[str, SafeData]]:
resolved_filepaths, _ = resolve_media_file(
filepath,
allow_glob,
static_files_dir=matched_component_dir_abs,
media_root_dir=matched_component_dir_abs,
)
return resolved_filepaths
# Check if template name is a local file or not # Check if template name is a local file or not
if getattr(comp_media, "template_file", None): if getattr(comp_media, "template_file", None):
comp_media.template_file = resolve_media_file(comp_media.template_file) comp_media.template_file = resolve_relative_media_file(comp_media.template_file, False)[0]
if getattr(comp_media, "js_file", None): if getattr(comp_media, "js_file", None):
comp_media.js_file = resolve_media_file(comp_media.js_file) comp_media.js_file = resolve_relative_media_file(comp_media.js_file, False)[0]
if getattr(comp_media, "css_file", None): if getattr(comp_media, "css_file", None):
comp_media.css_file = resolve_media_file(comp_media.css_file) comp_media.css_file = resolve_relative_media_file(comp_media.css_file, False)[0]
if hasattr(comp_media, "Media") and comp_media.Media: if hasattr(comp_media, "Media") and comp_media.Media:
_map_media_filepaths(comp_media.Media, resolve_media_file) _map_media_filepaths(
comp_media.Media,
# Media files can be defined as a glob patterns that match multiple files.
def _get_dir_path_from_component_path( # Thus, flatten the list of lists returned by `resolve_relative_media_file`.
abs_component_file_path: str, lambda filepaths: flatten(resolve_relative_media_file(f, True) for f in filepaths),
candidate_dirs: Union[List[str], List[Path]],
) -> Tuple[str, str]:
comp_dir_path_abs = os.path.dirname(abs_component_file_path)
# From all dirs defined in settings.COMPONENTS.dirs, find one that's the parent
# to the component file.
root_dir_abs = None
for candidate_dir in candidate_dirs:
candidate_dir_abs = os.path.abspath(candidate_dir)
if comp_dir_path_abs.startswith(candidate_dir_abs):
root_dir_abs = candidate_dir_abs
break
if root_dir_abs is None:
raise RuntimeError(
f"Failed to resolve template directory for component file '{abs_component_file_path}'",
) )
# Derive the path from matched COMPONENTS.dirs to the dir where the current component file is. # Go over the JS / CSS media files again, but this time, if there are still any globs,
comp_dir_path_rel = os.path.relpath(comp_dir_path_abs, candidate_dir_abs) # try to resolve them relative to the root directory (as defined by `COMPONENTS.dirs
# and `COMPONENTS.app_dirs` settings).
_map_media_filepaths(
comp_media.Media,
# Media files can be defined as a glob patterns that match multiple files.
# Thus, flatten the list of lists returned by `resolve_static_media_file`.
lambda filepaths: flatten(resolve_static_media_file(f, True) for f in filepaths),
)
# Return both absolute and relative paths:
# - Absolute path is used to check if the file exists # Check if filepath refers to a file that's in the same directory as the component class.
# - Relative path is used for defining the import on the component class # If yes, modify the path to refer to the relative file.
return comp_dir_path_abs, comp_dir_path_rel # If not, don't modify anything.
def resolve_media_file(
filepath: Union[str, SafeData],
allow_glob: bool,
static_files_dir: str,
media_root_dir: str,
) -> Tuple[List[Union[str, SafeData]], bool]:
# If filepath is NOT a string, then return as is
if not isinstance(filepath, str):
return [filepath], False
filepath_abs_or_glob = os.path.join(media_root_dir, filepath)
# The path may be a glob. So before we check if the file exists,
# we need to resolve the glob.
if allow_glob and is_glob(filepath_abs_or_glob):
matched_abs_filepaths = glob.glob(filepath_abs_or_glob)
else:
matched_abs_filepaths = [filepath_abs_or_glob]
# If there are no matches, return the original filepath
if not matched_abs_filepaths:
return [filepath], False
resolved_filepaths: List[str] = []
for matched_filepath_abs in matched_abs_filepaths:
# Derive the path from matched `COMPONENTS.dirs` to the media file.
# NOTE: The paths to resources need to use POSIX (forward slashes) for Django to work
# See https://github.com/django-components/django-components/issues/796
# NOTE: Since these paths matched the glob, we know that these files exist.
filepath_rel_to_comp_dir = Path(os.path.relpath(matched_filepath_abs, static_files_dir)).as_posix()
resolved_filepaths.append(filepath_rel_to_comp_dir)
return resolved_filepaths, True
def _find_component_dir_containing_file(
component_dirs: Sequence[Union[str, Path]],
target_file_path: str,
) -> Optional[Union[str, Path]]:
"""
From all directories that may contain components (such as those defined in `COMPONENTS.dirs`),
find the one that's the parent to the given file.
"""
abs_target_file_path = os.path.abspath(target_file_path)
for component_dir in component_dirs:
component_dir_abs = os.path.abspath(component_dir)
if abs_target_file_path.startswith(component_dir_abs):
return component_dir
return None
def _get_asset( def _get_asset(

View file

@ -2,8 +2,9 @@ import re
import sys import sys
from hashlib import md5 from hashlib import md5
from importlib import import_module from importlib import import_module
from itertools import chain
from types import ModuleType from types import ModuleType
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Tuple, Type, TypeVar, Union from typing import TYPE_CHECKING, Any, Callable, Iterable, List, Optional, Tuple, Type, TypeVar, Union
from django_components.util.nanoid import generate from django_components.util.nanoid import generate
@ -115,3 +116,15 @@ def hash_comp_cls(comp_cls: Type["Component"]) -> str:
full_name = get_import_path(comp_cls) full_name = get_import_path(comp_cls)
comp_cls_hash = md5(full_name.encode()).hexdigest()[0:6] comp_cls_hash = md5(full_name.encode()).hexdigest()[0:6]
return comp_cls.__name__ + "_" + comp_cls_hash return comp_cls.__name__ + "_" + comp_cls_hash
# String is a glob if it contains at least one of `?`, `*`, or `[`
is_glob_re = re.compile(r"[?*[]")
def is_glob(filepath: str) -> bool:
return is_glob_re.search(filepath) is not None
def flatten(lst: Iterable[Iterable[T]]) -> List[T]:
return list(chain.from_iterable(lst))

View file

@ -0,0 +1,23 @@
from django_components import Component
# The Media JS / CSS glob and are relative to the component directory
class GlobComponent(Component):
template = """
{% load component_tags %}
{% component_js_dependencies %}
{% component_css_dependencies %}
"""
class Media:
css = "glob_*.css"
js = "glob_*.js"
# The Media JS / CSS glob and are relative to the directory given in
# `COMPONENTS.dirs` and `COMPONENTS.app_dirs`
class GlobComponentRootDir(GlobComponent):
class Media:
css = "glob/glob_*.css"
js = "glob/glob_*.js"

View file

@ -0,0 +1,3 @@
.html-css-only {
color: blue;
}

View file

@ -0,0 +1 @@
console.log("JS file");

View file

@ -0,0 +1,3 @@
.html-css-only {
color: blue;
}

View file

@ -0,0 +1 @@
console.log("JS file");

View file

@ -386,6 +386,34 @@ class TestComponentMedia:
assertInHTML('<link abc href="path/to/style2.css" media="print" rel="stylesheet">', rendered) assertInHTML('<link abc href="path/to/style2.css" media="print" rel="stylesheet">', rendered)
assertInHTML('<link abc href="path/to/style3.css" media="screen" rel="stylesheet">', rendered) assertInHTML('<link abc href="path/to/style3.css" media="screen" rel="stylesheet">', rendered)
@djc_test(
django_settings={
"INSTALLED_APPS": ("django_components", "tests"),
}
)
def test_glob_pattern_relative_to_component(self):
from tests.components.glob.glob import GlobComponent
rendered = GlobComponent.render()
assertInHTML('<link href="glob/glob_1.css" media="all" rel="stylesheet">', rendered)
assertInHTML('<link href="glob/glob_2.css" media="all" rel="stylesheet">', rendered)
assertInHTML('<script src="glob/glob_1.js"></script>', rendered)
assertInHTML('<script src="glob/glob_2.js"></script>', rendered)
@djc_test(
django_settings={
"INSTALLED_APPS": ("django_components", "tests"),
}
)
def test_glob_pattern_relative_to_root_dir(self):
from tests.components.glob.glob import GlobComponentRootDir
rendered = GlobComponentRootDir.render()
assertInHTML('<link href="glob/glob_1.css" media="all" rel="stylesheet">', rendered)
assertInHTML('<link href="glob/glob_2.css" media="all" rel="stylesheet">', rendered)
assertInHTML('<script src="glob/glob_1.js"></script>', rendered)
assertInHTML('<script src="glob/glob_2.js"></script>', rendered)
@djc_test @djc_test
class TestMediaPathAsObject: class TestMediaPathAsObject:

View file

@ -247,6 +247,7 @@ class TestComponentFiles:
assert dot_paths == [ assert dot_paths == [
"components", "components",
"components.glob.glob",
"components.multi_file.multi_file", "components.multi_file.multi_file",
"components.relative_file.relative_file", "components.relative_file.relative_file",
"components.relative_file_pathobj.relative_file_pathobj", "components.relative_file_pathobj.relative_file_pathobj",
@ -260,15 +261,16 @@ class TestComponentFiles:
# NOTE: Compare parts so that the test works on Windows too # NOTE: Compare parts so that the test works on Windows too
assert file_paths[0].parts[-3:] == ("tests", "components", "__init__.py") assert file_paths[0].parts[-3:] == ("tests", "components", "__init__.py")
assert file_paths[1].parts[-4:] == ("tests", "components", "multi_file", "multi_file.py") assert file_paths[1].parts[-4:] == ("tests", "components", "glob", "glob.py")
assert file_paths[2].parts[-4:] == ("tests", "components", "relative_file", "relative_file.py") assert file_paths[2].parts[-4:] == ("tests", "components", "multi_file", "multi_file.py")
assert file_paths[3].parts[-4:] == ("tests", "components", "relative_file_pathobj", "relative_file_pathobj.py") assert file_paths[3].parts[-4:] == ("tests", "components", "relative_file", "relative_file.py")
assert file_paths[4].parts[-3:] == ("tests", "components", "single_file.py") assert file_paths[4].parts[-4:] == ("tests", "components", "relative_file_pathobj", "relative_file_pathobj.py")
assert file_paths[5].parts[-4:] == ("tests", "components", "staticfiles", "staticfiles.py") assert file_paths[5].parts[-3:] == ("tests", "components", "single_file.py")
assert file_paths[6].parts[-3:] == ("tests", "components", "urls.py") assert file_paths[6].parts[-4:] == ("tests", "components", "staticfiles", "staticfiles.py")
assert file_paths[7].parts[-3:] == ("django_components", "components", "__init__.py") assert file_paths[7].parts[-3:] == ("tests", "components", "urls.py")
assert file_paths[8].parts[-3:] == ("django_components", "components", "dynamic.py") assert file_paths[8].parts[-3:] == ("django_components", "components", "__init__.py")
assert file_paths[9].parts[-5:] == ("tests", "test_app", "components", "app_lvl_comp", "app_lvl_comp.py") assert file_paths[9].parts[-3:] == ("django_components", "components", "dynamic.py")
assert file_paths[10].parts[-5:] == ("tests", "test_app", "components", "app_lvl_comp", "app_lvl_comp.py")
@djc_test( @djc_test(
django_settings={ django_settings={
@ -282,6 +284,8 @@ class TestComponentFiles:
file_paths = [f.filepath for f in files] file_paths = [f.filepath for f in files]
assert dot_paths == [ assert dot_paths == [
"components.glob.glob_1",
"components.glob.glob_2",
"components.relative_file.relative_file", "components.relative_file.relative_file",
"components.relative_file_pathobj.relative_file_pathobj", "components.relative_file_pathobj.relative_file_pathobj",
"components.staticfiles.staticfiles", "components.staticfiles.staticfiles",
@ -289,10 +293,12 @@ class TestComponentFiles:
] ]
# NOTE: Compare parts so that the test works on Windows too # NOTE: Compare parts so that the test works on Windows too
assert file_paths[0].parts[-4:] == ("tests", "components", "relative_file", "relative_file.js") assert file_paths[0].parts[-4:] == ("tests", "components", "glob", "glob_1.js")
assert file_paths[1].parts[-4:] == ("tests", "components", "relative_file_pathobj", "relative_file_pathobj.js") assert file_paths[1].parts[-4:] == ("tests", "components", "glob", "glob_2.js")
assert file_paths[2].parts[-4:] == ("tests", "components", "staticfiles", "staticfiles.js") assert file_paths[2].parts[-4:] == ("tests", "components", "relative_file", "relative_file.js")
assert file_paths[3].parts[-5:] == ("tests", "test_app", "components", "app_lvl_comp", "app_lvl_comp.js") assert file_paths[3].parts[-4:] == ("tests", "components", "relative_file_pathobj", "relative_file_pathobj.js")
assert file_paths[4].parts[-4:] == ("tests", "components", "staticfiles", "staticfiles.js")
assert file_paths[5].parts[-5:] == ("tests", "test_app", "components", "app_lvl_comp", "app_lvl_comp.js")
@djc_test @djc_test