|
3 | 3 | import contextlib |
4 | 4 | import logging |
5 | 5 | import re |
| 6 | +from dataclasses import dataclass |
| 7 | +from dataclasses import field |
6 | 8 | from pathlib import Path |
| 9 | +from typing import Any |
7 | 10 |
|
8 | 11 | import pytest |
9 | 12 | from django.conf import settings |
@@ -84,6 +87,37 @@ def _override_app_settings(**kwargs): |
84 | 87 | return _override_app_settings |
85 | 88 |
|
86 | 89 |
|
| 90 | +@dataclass |
| 91 | +class TestComponent: |
| 92 | + name: str |
| 93 | + content: str |
| 94 | + parent_dir: str = "bird" |
| 95 | + sub_dir: str | None = None |
| 96 | + |
| 97 | + def create(self, base_dir: Path) -> Path: |
| 98 | + parent = base_dir / self.parent_dir |
| 99 | + parent.mkdir(exist_ok=True) |
| 100 | + |
| 101 | + if self.sub_dir is not None: |
| 102 | + dir = parent / self.sub_dir |
| 103 | + dir.mkdir(exist_ok=True) |
| 104 | + else: |
| 105 | + dir = parent |
| 106 | + |
| 107 | + template = dir / f"{self.name}.html" |
| 108 | + template.write_text(self.content) |
| 109 | + return template |
| 110 | + |
| 111 | + |
| 112 | +@dataclass |
| 113 | +class TestComponentCase: |
| 114 | + component: TestComponent |
| 115 | + template_content: str |
| 116 | + expected: str |
| 117 | + description: str = "" |
| 118 | + template_context: dict[str, Any] = field(default_factory=dict) |
| 119 | + |
| 120 | + |
87 | 121 | @pytest.fixture |
88 | 122 | def create_bird_dir(templates_dir): |
89 | 123 | def func(name): |
@@ -152,25 +186,21 @@ def _create_template(template_file: Path) -> DjangoTemplate: |
152 | 186 |
|
153 | 187 | @pytest.fixture |
154 | 188 | def normalize_whitespace(): |
155 | | - def func(text): |
156 | | - # this makes writing tests much easier, as it gets rid of any |
157 | | - # existing whitespace that may be present in the template file |
158 | | - |
| 189 | + def func(text: str) -> str: |
| 190 | + """Normalize whitespace in rendered template output""" |
159 | 191 | # multiple whitespace characters |
160 | 192 | text = re.sub(r"\s+", " ", text) |
161 | | - |
162 | 193 | # after opening tag, including when there are attributes |
163 | 194 | text = re.sub(r"<(\w+)(\s+[^>]*)?\s*>", r"<\1\2>", text) |
164 | | - |
165 | 195 | # before closing tag |
166 | 196 | text = re.sub(r"\s+>", ">", text) |
167 | | - |
168 | 197 | # after opening tag and before closing tag |
169 | 198 | text = re.sub(r">\s+<", "><", text) |
170 | | - |
171 | 199 | # immediately after opening tag (including attributes) or before closing tag |
172 | 200 | text = re.sub(r"(<\w+(?:\s+[^>]*)?>)\s+|\s+(<\/\w+>)", r"\1\2", text) |
173 | | - |
| 201 | + # between tags and text content |
| 202 | + text = re.sub(r">\s+([^<])", r">\1", text) |
| 203 | + text = re.sub(r"([^>])\s+<", r"\1<", text) |
174 | 204 | return text.strip() |
175 | 205 |
|
176 | 206 | return func |
|
0 commit comments