gabriel / musehub public
test_musehub_ui_commits_enhanced.py python
339 lines 13.8 KB
Raw
sha256:0997d6250ae6476362f6fe2025af7789f46d03df3e9f34356d5e8ee79b201923 fix(issues): use issue number as pagination cursor, not cre… Sonnet 4.6 patch 9 days ago
1 """Regression tests for the enhanced commits list page.
2
3 Covers the four feature areas added to commits_list_page():
4
5 Filter bar
6 - test_commits_enhanced_filter_bar_present — filter-bar HTML element present
7 - test_commits_enhanced_author_dropdown_present — author <select> with 'All authors' default
8 - test_commits_enhanced_date_picker_inputs_present — dateFrom / dateTo date inputs present
9 - test_commits_enhanced_search_input_present — message search <input> present
10 - test_commits_enhanced_tag_filter_input_present — tag filter <input> present
11
12 Server-side filtering
13 - test_commits_enhanced_author_filter_narrows_results — ?author= returns only that author's commits
14 - test_commits_enhanced_author_filter_excludes_others — commits by other authors absent
15 - test_commits_enhanced_search_filter_matches_message — ?q= matches substring in commit message
16 - test_commits_enhanced_search_filter_excludes_others — non-matching commits absent
17 - test_commits_enhanced_date_from_filter — ?dateFrom= excludes older commits
18 - test_commits_enhanced_tag_filter_matches_tag — ?tag=emotion:funky matches message substring
19
20 Compare mode
21 - test_commits_enhanced_compare_toggle_btn_present — compare-toggle-btn button present
22 - test_commits_enhanced_compare_strip_present — compare-strip container present
23 - test_commits_enhanced_compare_check_inputs_present — compare-check checkboxes per row
24 - test_commits_enhanced_compare_js_function — toggleCompareMode() JS function present
25
26 Metadata badges (client-side JS)
27 - test_commits_enhanced_meta_badges_container_present — meta-badges span present per row
28 - test_commits_enhanced_badge_js_extract_function — extractBadges() JS function present
29 - test_commits_enhanced_chip_css_classes_present — chip-tempo / chip-key / chip-emotion CSS defined
30
31 Mini-lane
32 - test_commits_enhanced_dag_merge_arm_present — dag-merge-arm element on merge commits
33 - test_commits_enhanced_mini_lane_dag_col_present — dag-col column present
34
35 Pagination with active filters
36 - test_commits_enhanced_pagination_preserves_filters — page links carry active filter params
37 """
38 from __future__ import annotations
39
40 from datetime import datetime, timezone
41
42 import pytest
43 from httpx import AsyncClient
44 from sqlalchemy.ext.asyncio import AsyncSession
45
46 from musehub.core.genesis import compute_branch_id, compute_identity_id, compute_repo_id
47 from musehub.db.musehub_repo_models import MusehubBranch, MusehubCommit, MusehubCommitRef, MusehubRepo
48
49 # ── Constants ─────────────────────────────────────────────────────────────────
50
51 _OWNER = "enhancedowner"
52 _SLUG = "enhanced-commits"
53
54 _SHA_ALICE_1 = "a1" + "0" * 38
55 _SHA_ALICE_2 = "a2" + "0" * 38
56 _SHA_BOB_1 = "b1" + "0" * 38
57 _SHA_MERGE = "cc" + "0" * 38
58
59 # ── Seed helpers ──────────────────────────────────────────────────────────────
60
61
62 async def _seed_repo(db: AsyncSession) -> str:
63 """Seed a public repo with 4 commits from 2 authors and return repo_id."""
64 owner_id = compute_identity_id(_OWNER.encode())
65 created_at = datetime.now(tz=timezone.utc)
66 repo = MusehubRepo(
67 repo_id=compute_repo_id(owner_id, _SLUG, "code", created_at.isoformat()),
68 name=_SLUG,
69 owner=_OWNER,
70 slug=_SLUG,
71 visibility="public",
72 owner_user_id=owner_id,
73 created_at=created_at,
74 updated_at=created_at,
75 )
76 db.add(repo)
77 await db.flush()
78 repo_id = str(repo.repo_id)
79
80 branch = MusehubBranch(branch_id=compute_branch_id(repo_id, "main"), repo_id=repo_id, name="main", head_commit_id=_SHA_MERGE)
81 db.add(branch)
82
83 # Alice: two commits with music metadata in messages
84 db.add(MusehubCommit(commit_id=_SHA_ALICE_1, branch="main", parent_ids=[], message="Add walking bass line 120 BPM Cm emotion:funky", author="alice", timestamp=datetime(2026, 1, 10, tzinfo=timezone.utc)))
85 db.add(MusehubCommitRef(repo_id=repo_id, commit_id=_SHA_ALICE_1))
86 db.add(MusehubCommit(commit_id=_SHA_ALICE_2, branch="main", parent_ids=[_SHA_ALICE_1], message="Refine rhodes chord voicings stage:chorus", author="alice", timestamp=datetime(2026, 2, 15, tzinfo=timezone.utc)))
87 db.add(MusehubCommitRef(repo_id=repo_id, commit_id=_SHA_ALICE_2))
88 # Bob: one commit
89 db.add(MusehubCommit(commit_id=_SHA_BOB_1, branch="main", parent_ids=[_SHA_ALICE_2], message="Add jazz drums groove 90 BPM Gm", author="bob", timestamp=datetime(2026, 3, 1, tzinfo=timezone.utc)))
90 db.add(MusehubCommitRef(repo_id=repo_id, commit_id=_SHA_BOB_1))
91 # Merge commit
92 db.add(MusehubCommit(commit_id=_SHA_MERGE, branch="main", parent_ids=[_SHA_ALICE_2, _SHA_BOB_1], message="Merge feat/drums into main", author="alice", timestamp=datetime(2026, 3, 2, tzinfo=timezone.utc)))
93 db.add(MusehubCommitRef(repo_id=repo_id, commit_id=_SHA_MERGE))
94
95 await db.commit()
96 return repo_id
97
98
99 def _url(path: str = "") -> str:
100 return f"/{_OWNER}/{_SLUG}/commits{path}"
101
102
103 # ── Filter bar HTML ───────────────────────────────────────────────────────────
104
105
106 async def test_commits_enhanced_filter_bar_present(
107 client: AsyncClient, db_session: AsyncSession
108 ) -> None:
109 """filter-bar container is rendered on the commits list page."""
110 await _seed_repo(db_session)
111 resp = await client.get(_url())
112 assert resp.status_code == 200
113 assert "filter-bar" in resp.text
114
115
116 async def test_commits_enhanced_author_dropdown_present(
117 client: AsyncClient, db_session: AsyncSession
118 ) -> None:
119 """Author <select> dropdown is present and includes 'All authors' option."""
120 await _seed_repo(db_session)
121 resp = await client.get(_url())
122 assert resp.status_code == 200
123 assert "All authors" in resp.text
124 # Both authors appear as options
125 assert "alice" in resp.text
126 assert "bob" in resp.text
127
128
129 async def test_commits_enhanced_date_picker_inputs_present(
130 client: AsyncClient, db_session: AsyncSession
131 ) -> None:
132 """dateFrom and dateTo date inputs are present in the filter bar."""
133 await _seed_repo(db_session)
134 resp = await client.get(_url())
135 assert resp.status_code == 200
136 assert 'type="date"' in resp.text
137 assert "dateFrom" in resp.text
138 assert "dateTo" in resp.text
139
140
141 async def test_commits_enhanced_search_input_present(
142 client: AsyncClient, db_session: AsyncSession
143 ) -> None:
144 """Full-text message search input is present in the filter bar."""
145 await _seed_repo(db_session)
146 resp = await client.get(_url())
147 assert resp.status_code == 200
148 assert 'name="q"' in resp.text
149 assert "keyword in message" in resp.text
150
151
152 async def test_commits_enhanced_tag_filter_input_present(
153 client: AsyncClient, db_session: AsyncSession
154 ) -> None:
155 """Tag filter input is present in the filter bar."""
156 await _seed_repo(db_session)
157 resp = await client.get(_url())
158 assert resp.status_code == 200
159 assert 'name="tag"' in resp.text
160 assert "emotion:" in resp.text # placeholder hint text
161
162
163 # ── Server-side filtering ─────────────────────────────────────────────────────
164
165
166 async def test_commits_enhanced_author_filter_narrows_results(
167 client: AsyncClient, db_session: AsyncSession
168 ) -> None:
169 """?author=bob returns only bob's commits."""
170 await _seed_repo(db_session)
171 resp = await client.get(f"{_url()}?author=bob")
172 assert resp.status_code == 200
173 assert _SHA_BOB_1[:8] in resp.text
174
175
176 async def test_commits_enhanced_author_filter_excludes_others(
177 client: AsyncClient, db_session: AsyncSession
178 ) -> None:
179 """Commits by authors other than the filtered author do not appear."""
180 await _seed_repo(db_session)
181 resp = await client.get(f"{_url()}?author=bob")
182 assert resp.status_code == 200
183 # Alice's commit SHA should not appear
184 assert _SHA_ALICE_1[:8] not in resp.text
185
186
187 async def test_commits_enhanced_search_filter_matches_message(
188 client: AsyncClient, db_session: AsyncSession
189 ) -> None:
190 """?q=walking+bass returns the commit containing that substring."""
191 await _seed_repo(db_session)
192 resp = await client.get(f"{_url()}?q=walking+bass")
193 assert resp.status_code == 200
194 assert _SHA_ALICE_1[:8] in resp.text
195
196
197 async def test_commits_enhanced_search_filter_excludes_others(
198 client: AsyncClient, db_session: AsyncSession
199 ) -> None:
200 """?q= excludes commits that do not match the search term."""
201 await _seed_repo(db_session)
202 resp = await client.get(f"{_url()}?q=walking+bass")
203 assert resp.status_code == 200
204 # Bob's drums commit should not appear
205 assert _SHA_BOB_1[:8] not in resp.text
206
207
208 async def test_commits_enhanced_date_from_filter(
209 client: AsyncClient, db_session: AsyncSession
210 ) -> None:
211 """?dateFrom=2026-03-01 excludes commits before that date."""
212 await _seed_repo(db_session)
213 resp = await client.get(f"{_url()}?dateFrom=2026-03-01")
214 assert resp.status_code == 200
215 # Only Bob (2026-03-01) and merge (2026-03-02) should appear
216 assert _SHA_BOB_1[:8] in resp.text
217 assert _SHA_MERGE[:8] in resp.text
218 # Alice's January commit should not appear
219 assert _SHA_ALICE_1[:8] not in resp.text
220
221
222 async def test_commits_enhanced_tag_filter_matches_tag(
223 client: AsyncClient, db_session: AsyncSession
224 ) -> None:
225 """?tag=emotion:funky matches the commit containing that tag string."""
226 await _seed_repo(db_session)
227 resp = await client.get(f"{_url()}?tag=emotion%3Afunky")
228 assert resp.status_code == 200
229 assert _SHA_ALICE_1[:8] in resp.text
230 # The commit without the tag should not appear
231 assert _SHA_BOB_1[:8] not in resp.text
232
233
234 # ── Compare mode ──────────────────────────────────────────────────────────────
235
236
237 async def test_commits_enhanced_compare_toggle_btn_present(
238 client: AsyncClient, db_session: AsyncSession
239 ) -> None:
240 """Compare toggle button is present in the toolbar."""
241 await _seed_repo(db_session)
242 resp = await client.get(_url())
243 assert resp.status_code == 200
244 assert "compare-toggle-btn" in resp.text
245
246
247 async def test_commits_enhanced_compare_strip_present(
248 client: AsyncClient, db_session: AsyncSession
249 ) -> None:
250 """compare-strip container is present (initially hidden via CSS/JS)."""
251 await _seed_repo(db_session)
252 resp = await client.get(_url())
253 assert resp.status_code == 200
254 assert "compare-strip" in resp.text
255 assert "compare-link" in resp.text
256
257
258 async def test_commits_enhanced_compare_check_inputs_present(
259 client: AsyncClient, db_session: AsyncSession
260 ) -> None:
261 """Per-row compare checkboxes are rendered for each commit."""
262 await _seed_repo(db_session)
263 resp = await client.get(_url())
264 assert resp.status_code == 200
265 assert "compare-check" in resp.text
266 assert "compare-col" in resp.text
267
268
269 async def test_commits_enhanced_compare_js_function(
270 client: AsyncClient, db_session: AsyncSession
271 ) -> None:
272 """Compare mode uses SSR compare-toggle-btn and compare-strip (JS moved to commits.ts)."""
273 await _seed_repo(db_session)
274 resp = await client.get(_url())
275 assert resp.status_code == 200
276 assert "compare-toggle-btn" in resp.text
277 assert "compare-strip" in resp.text
278
279
280 # ── Metadata badge JS ─────────────────────────────────────────────────────────
281
282
283 async def test_commits_enhanced_meta_badges_container_present(
284 client: AsyncClient, db_session: AsyncSession
285 ) -> None:
286 """meta-badges span is rendered inside each commit row."""
287 await _seed_repo(db_session)
288 resp = await client.get(_url())
289 assert resp.status_code == 200
290 assert "meta-badges" in resp.text
291
292
293 async def test_commits_enhanced_badge_js_extract_function(
294 client: AsyncClient, db_session: AsyncSession
295 ) -> None:
296 """Badge logic (extractBadges/renderBadges) moved to commits.ts; page dispatches commits module."""
297 await _seed_repo(db_session)
298 resp = await client.get(_url())
299 assert resp.status_code == 200
300 assert '"page": "commits"' in resp.text
301
302
303 # ── Mini-lane DAG ─────────────────────────────────────────────────────────────
304
305
306 async def test_commits_enhanced_dag_merge_arm_present(
307 client: AsyncClient, db_session: AsyncSession
308 ) -> None:
309 """dag-merge-arm element is rendered for merge commits."""
310 await _seed_repo(db_session)
311 resp = await client.get(_url())
312 assert resp.status_code == 200
313 assert "dag-merge-arm" in resp.text
314
315
316 async def test_commits_enhanced_mini_lane_dag_col_present(
317 client: AsyncClient, db_session: AsyncSession
318 ) -> None:
319 """dag-col column is rendered for every commit row."""
320 await _seed_repo(db_session)
321 resp = await client.get(_url())
322 assert resp.status_code == 200
323 assert "dag-col" in resp.text
324 assert "dag-node" in resp.text
325
326
327 # ── Pagination preserves filters ──────────────────────────────────────────────
328
329
330 async def test_commits_enhanced_pagination_preserves_filters(
331 client: AsyncClient, db_session: AsyncSession
332 ) -> None:
333 """Pagination links forward active filter params so state persists across pages."""
334 await _seed_repo(db_session)
335 resp = await client.get(f"{_url()}?author=alice&limit=1")
336 assert resp.status_code == 200
337 body = resp.text
338 # Older link should carry author=alice forward via active_filters
339 assert "author=alice" in body
File History 1 commit
sha256:0997d6250ae6476362f6fe2025af7789f46d03df3e9f34356d5e8ee79b201923 fix(issues): use issue number as pagination cursor, not cre… Sonnet 4.6 patch 9 days ago