from __future__ import annotations import unittest from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parents[1] STATIC_ROOT = PROJECT_ROOT / "arr_web" / "static" class TaskLogStaticContractTests(unittest.TestCase): @classmethod def setUpClass(cls) -> None: cls.html = (STATIC_ROOT / "index.html").read_text(encoding="utf-8") cls.styles = (STATIC_ROOT / "styles.css").read_text(encoding="utf-8") cls.script = (STATIC_ROOT / "app.js").read_text(encoding="utf-8") def test_header_utility_replaces_mobile_dashboard_entry(self) -> None: self.assertIn('id="task-log-trigger"', self.html) self.assertIn(' None: daily_start = self.html.index('id="panel-daily"') daily_end = self.html.index('id="panel-monthly"') daily_panel = self.html[daily_start:daily_end] self.assertNotIn('id="process-log"', daily_panel) self.assertEqual(self.html.count('id="process-log"'), 1) self.assertEqual(self.html.count('id="task-log-dialog"'), 1) self.assertGreater( self.html.index('id="task-log-dialog"'), self.html.index(""), ) for required_id in ( 'id="process-title"', 'id="trace-live-state"', 'id="refresh-trace"', 'id="copy-trace"', 'id="close-task-log"', ): self.assertIn(required_id, self.html) def test_dialog_open_close_and_polling_contract(self) -> None: self.assertIn('$("#task-log-trigger").addEventListener("click", (event) => {', self.script) self.assertIn("event.preventDefault();", self.script) self.assertIn("openTaskLog();", self.script) self.assertIn('$("#close-task-log").addEventListener("click", closeTaskLog)', self.script) self.assertIn('$("#task-log-dialog").addEventListener("close", clearTracePoll)', self.script) self.assertIn("|| !taskLogIsOpen()", self.script) self.assertIn("{ showLog: true }", self.script) self.assertNotIn('{ scroll: true }', self.script) def test_upload_keeps_progress_inline_without_opening_log(self) -> None: self.assertIn('id="upload-progress"', self.html) self.assertIn('id="upload-progress-track"', self.html) self.assertIn('role="progressbar"', self.html) self.assertIn("DAILY_UPLOAD_PROGRESS_STAGES", self.script) self.assertIn("function startUploadProgress()", self.script) self.assertIn("function finishUploadProgress(success, label)", self.script) upload_start = self.script.index("async function handleUpload()") upload_end = self.script.index("\n async function loadMonthsAndAnalytics", upload_start) upload_handler = self.script[upload_start:upload_end] self.assertIn("startUploadProgress()", upload_handler) self.assertIn("finishUploadProgress", upload_handler) self.assertNotIn("openTaskLog()", upload_handler) self.assertNotIn("showLog: true", upload_handler) def test_dialog_reuses_responsive_accessible_visual_system(self) -> None: self.assertIn(".task-log-link:hover", self.styles) self.assertIn(".task-log-link:active", self.styles) self.assertIn(".task-log-dialog::backdrop", self.styles) self.assertIn(".upload-progress-track", self.styles) self.assertIn(".upload-progress.is-success", self.styles) self.assertIn("max-height: calc(100dvh - 32px)", self.styles) self.assertIn("@media (max-width: 700px)", self.styles) self.assertIn("@media (prefers-reduced-motion: reduce)", self.styles) if __name__ == "__main__": unittest.main(verbosity=2)