import json import sys from pathlib import Path _HERE = Path(__file__).resolve().parents[2] if str(_HERE) not in sys.path: sys.path.insert(0, str(_HERE)) from app import ( _DEFAULT_FLOW_NAME_SLUGS, _default_window_geometry, _flow_name_for_mode, _format_geometry, _format_live_action, _merge_current_live_action, ) import artifacts as art import target import user_prefs def test_default_geometry_is_narrow_tall_and_leaves_title_bar_clear() -> None: width, height, x, y = _default_window_geometry((0, 0, 1920, 1040)) assert (width, height) == (576, 951) assert (x, y) == (1328, 73) assert y >= 64 assert x + width == 1920 - 16 def test_default_geometry_supports_small_and_left_side_monitors() -> None: width, height, x, y = _default_window_geometry( (-1366, 0, 0, 728), side="left" ) assert (width, height) == (560, 648) assert (x, y) == (-1350, 64) assert _format_geometry(width, height, x, y) == "560x648-1350+64" def test_flow_name_defaults_follow_pick_mode() -> None: assert _flow_name_for_mode(target.SURFACE_BROWSER) == "browser" assert _flow_name_for_mode(target.SURFACE_WINDOWS) == "desktop" assert _flow_name_for_mode(target.SURFACE_ANDROID) == "mobile" assert _DEFAULT_FLOW_NAME_SLUGS == frozenset({"browser", "desktop", "mobile"}) def test_local_ts_is_compact_yyyymmddhhmmss() -> None: stamp = art._local_ts() assert len(stamp) == 14 assert stamp.isdigit() assert "T" not in stamp assert "+" not in stamp def test_create_flow_dir_uses_compact_timestamp(tmp_path: Path) -> None: flow_dir = art.create_flow(tmp_path, flow_name="browser") name = flow_dir.name assert name.endswith("_browser") stamp = name[: -len("_browser")] assert len(stamp) == 14 assert stamp.isdigit() def test_hidden_legacy_recording_preference_migrates_to_auto( tmp_path: Path, monkeypatch ) -> None: prefs_path = tmp_path / "user_prefs.json" prefs_path.write_text( json.dumps( { "mode": "browser", "start_url": "https://example.com", "recording_mode": "legacy_interval", } ), encoding="utf-8", ) monkeypatch.setattr(user_prefs, "_PREFS_PATH", prefs_path) assert user_prefs.load_prefs()["recording_mode"] == "auto" def test_live_action_formatter_is_surface_neutral_and_two_phase() -> None: text = _format_live_action( { "sequence": 3, "action_type": "click", "phase": "captured", "surface": "browser", "target_name": "提交", "target_type": "button", } ) assert "#3 点击" in text assert "browser" in text assert "证据采集中" in text assert "等待异步证据" in text def test_current_live_action_overwrites_without_allowing_late_evidence_to_regress() -> None: first = {"id": "action-1", "sequence": 1, "phase": "captured"} first_evidence = {"id": "action-1", "sequence": 1, "phase": "evidence"} second = {"id": "action-2", "sequence": 2, "phase": "captured"} current = _merge_current_live_action(None, first) current = _merge_current_live_action(current, first_evidence) assert current == first_evidence current = _merge_current_live_action(current, second) assert current == second current = _merge_current_live_action(current, first_evidence) assert current == second def test_current_live_action_ignores_updates_without_an_action_id() -> None: current = {"id": "action-2", "sequence": 2, "phase": "captured"} assert _merge_current_live_action(current, {"sequence": 3}) is current