gabriel / musehub public

test_musehub_jinja2_macros.py file-level

at sha256:3 · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 💥 blast risk
sha256:0 fix: fall back to any indexed mpack in read_object_bytes when push mpac… · gabriel · Jun 17, 2026
1 """Tests for MuseHub Jinja2 server-side filters and component macros.
2
3 Covers every filter function in jinja2_filters.py and spot-checks macro
4 rendering via a real Jinja2 Environment backed by the on-disk template tree.
5 """
6 from __future__ import annotations
7
8 from datetime import datetime, timedelta, timezone
9 from pathlib import Path
10
11 import pytest
12 from jinja2 import Environment, FileSystemLoader
13
14 from musehub.api.routes.musehub.jinja2_filters import (
15 _fmtdate,
16 _fmtrelative,
17 _label_text_color,
18 _shortsha,
19 register_musehub_filters,
20 )
21 from musehub.api.routes.musehub._templates import _icon
22
23
24 # ---------------------------------------------------------------------------
25 # Fixtures
26 # ---------------------------------------------------------------------------
27
28
29 @pytest.fixture(scope="module")
30 def jinja_env() -> Environment:
31 """Real Jinja2 Environment pointed at the MuseHub template tree."""
32 template_dir = Path(__file__).parent.parent / "musehub" / "templates"
33 env = Environment(loader=FileSystemLoader(str(template_dir)), autoescape=True)
34 register_musehub_filters(env)
35 env.globals["icon"] = _icon
36 return env
37
38
39 # ---------------------------------------------------------------------------
40 # _fmtdate filter
41 # ---------------------------------------------------------------------------
42
43
44 def test_fmtdate_formats_datetime() -> None:
45 result = _fmtdate(datetime(2025, 1, 15, 10, 0, 0))
46 assert result == "Jan 15, 2025"
47
48
49 def test_fmtdate_formats_iso_string() -> None:
50 result = _fmtdate("2025-01-15T10:00:00Z")
51 assert result == "Jan 15, 2025"
52
53
54 def test_fmtdate_none_returns_empty() -> None:
55 assert _fmtdate(None) == ""
56
57
58 def test_fmtdate_iso_string_with_offset() -> None:
59 result = _fmtdate("2025-06-01T08:00:00+00:00")
60 assert result == "Jun 1, 2025"
61
62
63 # ---------------------------------------------------------------------------
64 # _fmtrelative filter
65 # ---------------------------------------------------------------------------
66
67
68 def test_fmtrelative_seconds() -> None:
69 value = datetime.now(timezone.utc) - timedelta(seconds=10)
70 assert _fmtrelative(value) == "just now"
71
72
73 def test_fmtrelative_one_minute() -> None:
74 value = datetime.now(timezone.utc) - timedelta(seconds=90)
75 assert _fmtrelative(value) == "1 minute ago"
76
77
78 def test_fmtrelative_hours() -> None:
79 value = datetime.now(timezone.utc) - timedelta(hours=2)
80 assert _fmtrelative(value) == "2 hours ago"
81
82
83 def test_fmtrelative_one_hour() -> None:
84 value = datetime.now(timezone.utc) - timedelta(hours=1)
85 assert _fmtrelative(value) == "1 hour ago"
86
87
88 def test_fmtrelative_days() -> None:
89 value = datetime.now(timezone.utc) - timedelta(days=3)
90 assert _fmtrelative(value) == "3 days ago"
91
92
93 def test_fmtrelative_one_day() -> None:
94 value = datetime.now(timezone.utc) - timedelta(days=1)
95 assert _fmtrelative(value) == "1 day ago"
96
97
98 def test_fmtrelative_none_returns_empty() -> None:
99 assert _fmtrelative(None) == ""
100
101
102 def test_fmtrelative_iso_string() -> None:
103 value = (datetime.now(timezone.utc) - timedelta(hours=5)).isoformat()
104 result = _fmtrelative(value)
105 assert result == "5 hours ago"
106
107
108 def test_fmtrelative_naive_datetime_treated_as_utc() -> None:
109 """Timezone-naive datetimes are assumed UTC per docstring contract."""
110 value = datetime.now(timezone.utc).replace(tzinfo=None) - timedelta(minutes=30)
111 result = _fmtrelative(value)
112 assert result == "30 minutes ago"
113
114
115 # ---------------------------------------------------------------------------
116 # _shortsha filter
117 # ---------------------------------------------------------------------------
118
119
120 def test_shortsha_returns_8_chars() -> None:
121 assert _shortsha("a1b2c3d4e5f6") == "a1b2c3d4"
122
123
124 def test_shortsha_already_short() -> None:
125 assert _shortsha("abc") == "abc"
126
127
128 def test_shortsha_none_returns_empty() -> None:
129 assert _shortsha(None) == ""
130
131
132 def test_shortsha_empty_string_returns_empty() -> None:
133 assert _shortsha("") == ""
134
135
136 def test_shortsha_exact_8_chars() -> None:
137 assert _shortsha("12345678") == "12345678"
138
139
140 # ---------------------------------------------------------------------------
141 # _label_text_color filter
142 # ---------------------------------------------------------------------------
143
144
145 def test_label_text_color_dark_bg() -> None:
146 assert _label_text_color("#000000") == "#fff"
147
148
149 def test_label_text_color_light_bg() -> None:
150 assert _label_text_color("#ffffff") == "#000"
151
152
153 def test_label_text_color_mid_green() -> None:
154 """Bright green (#3fb950) has high luminance — dark text is more readable."""
155 assert _label_text_color("#3fb950") == "#000"
156
157
158 def test_label_text_color_without_hash() -> None:
159 assert _label_text_color("ffffff") == "#000"
160
161
162 def test_label_text_color_malformed_returns_dark() -> None:
163 assert _label_text_color("#xyz") == "#000"
164
165
166 def test_label_text_color_red() -> None:
167 assert _label_text_color("#ff0000") == "#fff"
168
169
170 # ---------------------------------------------------------------------------
171 # register_musehub_filters — environment registration
172 # ---------------------------------------------------------------------------
173
174
175 def test_jinja2_env_has_fmtdate_filter(jinja_env: Environment) -> None:
176 assert "fmtdate" in jinja_env.filters
177
178
179 def test_jinja2_env_has_fmtrelative_filter(jinja_env: Environment) -> None:
180 assert "fmtrelative" in jinja_env.filters
181
182
183 def test_jinja2_env_has_shortsha_filter(jinja_env: Environment) -> None:
184 assert "shortsha" in jinja_env.filters
185
186
187 def test_jinja2_env_has_label_text_color_filter(jinja_env: Environment) -> None:
188 assert "label_text_color" in jinja_env.filters
189
190
191 def test_fmtdate_filter_via_env(jinja_env: Environment) -> None:
192 tmpl = jinja_env.from_string('{{ "2025-01-15T10:00:00Z" | fmtdate }}')
193 assert tmpl.render() == "Jan 15, 2025"
194
195
196 def test_shortsha_filter_via_env(jinja_env: Environment) -> None:
197 tmpl = jinja_env.from_string('{{ sha | shortsha }}')
198 assert tmpl.render(sha="abcdef1234567890") == "abcdef12"
199
200
201 # ---------------------------------------------------------------------------
202 # Macro rendering — issue_row
203 # ---------------------------------------------------------------------------
204
205
206 class _FakeIssue:
207 issueId = "i-1"
208 number = 42
209 title = "Fix timing issue in drum track"
210 state = "open"
211 labels: list[str] = ["bug", "audio"]
212 createdAt = "2025-01-15T10:00:00Z"
213 created_at = "2025-01-15T10:00:00Z"
214 author = "alice"
215
216
217 def test_issue_row_macro_renders_title(jinja_env: Environment) -> None:
218 tmpl = jinja_env.get_template("musehub/macros/issue.html")
219 macro = getattr(tmpl.module, "issue_row")
220 html = macro(_FakeIssue(), base_url="/alice/myrepo")
221 assert "Fix timing issue in drum track" in html
222
223
224 def test_issue_row_macro_renders_issue_number(jinja_env: Environment) -> None:
225 tmpl = jinja_env.get_template("musehub/macros/issue.html")
226 macro = getattr(tmpl.module, "issue_row")
227 html = macro(_FakeIssue(), base_url="/alice/myrepo")
228 assert "#42" in html
229
230
231 def test_issue_row_macro_renders_date(jinja_env: Environment) -> None:
232 tmpl = jinja_env.get_template("musehub/macros/issue.html")
233 macro = getattr(tmpl.module, "issue_row")
234 html = macro(_FakeIssue(), base_url="/alice/myrepo")
235 assert "Jan 15, 2025" in html
236
237
238 # ---------------------------------------------------------------------------
239 # Macro rendering — pagination
240 # ---------------------------------------------------------------------------
241
242
243 def test_pagination_macro_renders_prev_next(jinja_env: Environment) -> None:
244 tmpl = jinja_env.get_template("musehub/macros/pagination.html")
245 macro = getattr(tmpl.module, "pagination")
246 html = macro(page=2, total_pages=5, url="/alice/myrepo/issues")
247 assert "Prev" in html
248 assert "Next" in html
249 assert "Page 2 of 5" in html
250
251
252 def test_pagination_macro_hidden_on_single_page(jinja_env: Environment) -> None:
253 tmpl = jinja_env.get_template("musehub/macros/pagination.html")
254 macro = getattr(tmpl.module, "pagination")
255 html = macro(page=1, total_pages=1, url="/alice/myrepo/issues")
256 assert html.strip() == ""
257
258
259 def test_pagination_macro_no_prev_on_first_page(jinja_env: Environment) -> None:
260 tmpl = jinja_env.get_template("musehub/macros/pagination.html")
261 macro = getattr(tmpl.module, "pagination")
262 html = macro(page=1, total_pages=3, url="/alice/myrepo/issues")
263 assert "Prev" not in html
264 assert "Next" in html
265
266
267 # ---------------------------------------------------------------------------
268 # Macro rendering — empty_state
269 # ---------------------------------------------------------------------------
270
271
272 def test_empty_state_macro_renders_action(jinja_env: Environment) -> None:
273 tmpl = jinja_env.get_template("musehub/macros/empty_state.html")
274 macro = getattr(tmpl.module, "empty_state")
275 html = macro(
276 "📭",
277 "No issues yet",
278 "Open an issue to start tracking work.",
279 action_url="/new",
280 action_label="Open an issue",
281 )
282 assert "No issues yet" in html
283 assert "Open an issue" in html
284 assert 'href="/new"' in html
285
286
287 def test_empty_state_macro_no_action_when_url_missing(jinja_env: Environment) -> None:
288 tmpl = jinja_env.get_template("musehub/macros/empty_state.html")
289 macro = getattr(tmpl.module, "empty_state")
290 html = macro("📭", "No issues yet", "Nothing here.")
291 assert "btn" not in html