import unittest from pathlib import Path from frigate_camera_control_bridge.ui import camera_stream_url, render_page class DummyAction: def __init__(self, suffix="siren", name_key="action.siren", state="ON", kind="dahua_coaxial_io"): self.id = f"example_camera_{suffix}" self.platform = "switch" self.kind = kind self.state = state self.auto_off_seconds = 180 self.auto_off_at = 1234567890 self.snapshot_version = 42 self.cfg = { "name_key": name_key, "camera_name": "Example camera", "requires_ack": name_key == "action.siren", "snapshot": { "camera": "example", "frigate_url": "http://frigate.example.test:5000", }, } def display_name(self, lang): return f"Example {self.id}" def command_list(self): return ["on", "off"] class UiRenderTest(unittest.TestCase): def test_timer_timestamp_and_ack_survive_server_render(self): html = render_page([DummyAction()], "en") self.assertIn('data-auto-off-at="1234567890"', html) self.assertIn('data-requires-ack="true"', html) self.assertIn("Example camera", html) self.assertIn("v=42", html) self.assertIn('class="snapshot-refresh"', html) self.assertIn('title="Refresh snapshot"', html) self.assertIn('disabled title="Already active until automatic turn-off"', html) def test_controls_from_same_camera_share_one_card(self): actions = [ DummyAction("siren", "action.siren"), DummyAction("white", "action.manual_white", state="OFF"), DummyAction("red_blue", "action.red_blue", state="OFF"), ] html = render_page(actions, "en") self.assertIn('class="control-grid grid-single"', html) self.assertEqual(html.count('class="camera-card"'), 1) self.assertEqual(html.count('class="action-control"'), 3) self.assertIn("3 controls", html) self.assertIn("White light", html) self.assertIn("Red-blue light", html) self.assertIn("Siren", html) def test_all_off_renders_in_topbar_not_camera_grid(self): action = DummyAction("all_off", "action.all_off", state=None, kind="all_off") html = render_page([action], "en") self.assertIn('class="global-strip"', html) self.assertIn('class="global-action"', html) self.assertNotIn('class="camera-card"', html) def test_refresh_button_preserves_language_and_token(self): html = render_page([DummyAction()], "en", token_param="secret") self.assertIn('class="refresh-form"', html) self.assertIn('action="/refresh?lang=en&token=secret"', html) self.assertIn('title="Refresh cameras"', html) self.assertIn('aria-label="Refresh cameras"', html) self.assertIn('data-refresh-status', html) self.assertIn('data-working-label="Refreshing cameras..."', html) self.assertIn("Camera Friendly", html) self.assertNotIn("

camera_key

", html) def test_manual_snapshot_refresh_waits_before_fetch(self): script = Path("frigate_camera_control_bridge/static/app.js").read_text(encoding="utf-8") self.assertIn("SNAPSHOT_REFRESH_DELAY_MS = 5000", script) self.assertIn("window.setTimeout", script) def test_command_forms_expose_ajax_status_metadata(self): html = render_page([DummyAction()], "en") self.assertIn('class="command-form" method="post"', html) self.assertIn('type="submit"', html) self.assertIn('data-working-label="Sending command..."', html) self.assertIn('data-success-label="Command sent."', html) self.assertIn('data-network-failed-label="Could not reach the bridge.', html) self.assertIn('data-action-status', html) self.assertIn('data-state-chip', html) self.assertIn('data-timer', html) def test_command_submit_uses_fetch_without_page_navigation(self): script = Path("frigate_camera_control_bridge/static/app.js").read_text(encoding="utf-8") self.assertIn("handleCommandSubmit(form, event)", script) self.assertIn('form.classList.contains("command-form")', script) self.assertIn('headers: { Accept: "application/json" }', script) self.assertIn("event.preventDefault()", script) self.assertIn("commandErrorMessage", script) if __name__ == "__main__": unittest.main()