Files
hermes-webui/tests/test_issue1140_cron_badge_per_job.py
T
nesquena-hermes 8b8ff3328a fix: batch triage — 12 contributor PRs (v0.50.227) (#1168)
Merged as v0.50.227. 2634 tests passing, browser QA 21/21 (desktop + mobile). Full attribution below.

Thanks to all 12 contributors:
@jundev0001 (#1138), @franksong2702 (#1142, #1157, #1162), @dso2ng (#1143), @bergeouss (#1145, #1146, #1156, #1159), @jasonjcwu (#1149), @ccqqlo (#1161), @frap129 (#1165)

Two fixes applied during integration and two more by the independent reviewer (@nesquena):
- messages.js: per-turn cost delta capture order (#1159)
- workspace.py: symlink target blocked-roots check + HOME sanity guard (#1149, #1165)
- panels.js: cron unread counter bookkeeping (in-loop increment bug)
- tests/test_symlink_cycle_detection.py: register workspace before session/new
2026-04-27 13:34:59 -07:00

101 lines
3.8 KiB
Python

"""Tests for issue #1140 — Cron completion badge per-job indicator.
Verifies that:
1. _cronNewJobIds tracks job IDs with new completions
2. loadCrons() renders a dot indicator for new-run jobs
3. openCronDetail() clears the unread state for the viewed job
4. Badge only clears when all unread jobs are viewed (not on panel open)
5. _renderCronDetail() adds has-new-run class to Last Output card
"""
import pytest
# ── Static file tests (no server needed) ──
def test_cron_new_job_ids_tracking_in_panels_js():
"""panels.js should declare _cronNewJobIds Set and populate it in startCronPolling."""
with open('static/panels.js') as f:
src = f.read()
# _cronNewJobIds declared as Set
assert '_cronNewJobIds' in src, '_cronNewJobIds not found in panels.js'
assert 'new Set()' in src, '_cronNewJobIds should be initialized as Set()'
# In startCronPolling, job IDs are added to the set
assert '_cronNewJobIds.add(String(c.job_id))' in src, \
'startCronPolling should add job_id to _cronNewJobIds'
def test_cron_dot_indicator_rendered_in_load_crons():
"""loadCrons() should render a .cron-new-dot for jobs in _cronNewJobIds."""
with open('static/panels.js') as f:
src = f.read()
# Dot indicator in cron-item rendering
assert 'cron-new-dot' in src, 'cron-new-dot class not found'
assert "_cronNewJobIds.has(String(job.id))" in src, \
'loadCrons should check _cronNewJobIds for each job'
def test_open_cron_detail_clears_unread():
"""openCronDetail() should mark job as read and remove the dot."""
with open('static/panels.js') as f:
src = f.read()
# _clearCronUnreadForJob called in openCronDetail
assert '_clearCronUnreadForJob' in src, \
'_clearCronUnreadForJob function not found'
# Dot removal in openCronDetail
assert "target.querySelector('.cron-new-dot')" in src, \
'openCronDetail should remove the dot element'
def test_clear_cron_unread_for_job_function():
"""_clearCronUnreadForJob should delete from set and refresh badge.
_cronUnreadCount is derived from _cronNewJobIds.size in updateCronBadge,
so the function only needs to delete from the set and trigger a badge sync.
"""
with open('static/panels.js') as f:
src = f.read()
# Locate the function body to make assertions order-dependent
start = src.find('function _clearCronUnreadForJob(')
assert start != -1, '_clearCronUnreadForJob should be defined'
body = src[start:start + 400]
assert '_cronNewJobIds.delete(id)' in body, \
'_clearCronUnreadForJob should delete from _cronNewJobIds'
assert 'updateCronBadge()' in body, \
'_clearCronUnreadForJob should call updateCronBadge to re-sync count'
def test_switch_panel_no_longer_clears_badge():
"""switchPanel override should NOT clear badge on tasks panel open."""
with open('static/panels.js') as f:
src = f.read()
# The old pattern "if(name==='tasks'){_cronUnreadCount=0" should NOT exist
assert "if(name==='tasks'){_cronUnreadCount=0" not in src, \
'switchPanel should NOT clear _cronUnreadCount on tasks open'
def test_has_new_run_class_in_render_detail():
"""_renderCronDetail() should add has-new-run class to Last Output card."""
with open('static/panels.js') as f:
src = f.read()
# Check has-new-run class in the cronDetailRuns div
assert 'has-new-run' in src, 'has-new-run class not found'
def test_cron_css_classes_exist():
"""style.css should contain .cron-new-dot and .has-new-run styles."""
with open('static/style.css') as f:
src = f.read()
assert '.cron-new-dot{' in src, '.cron-new-dot CSS rule not found'
assert '.has-new-run{' in src, '.has-new-run CSS rule not found'
assert 'cron-dot-pulse' in src, 'cron-dot-pulse animation not found'