zapretgui/tests/test_launch_control.py
loop-uh de730e587e
All checks were successful
Zapret source guards / validate (push) Successful in 7s
Windows-сборка / Dev Dev 21.1.6.45 опубликован
Пуск и стоп Zapret отовсюду (#78): точка в «Статусе работы» стала выключателем (значок питания, подсветка при наведении, Enter/Пробел), секция из двух кнопок убрана — «Закрыть программу» тихой кнопкой в той же карточке; в заголовке окна рядом с PREMIUM метка «● Работает / Остановлен», видна в любом разделе и тоже переключает; трей переделан: шапка с режимом, состоянием и пресетом, первым пунктом «Запустить/Остановить Zapret», «Перезапустить», подменю пресетов с отметкой активного и быстрым переключением, меню собирается заново при каждом открытии; на иконке трея цветная точка (зелёная — работает, жёлтая — запуск/остановка), в подсказке режим и пресет, уведомления о запуске и остановке, когда окно скрыто; вся логика пуска в одном пульте ui/launch_control.py
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 14:07:07 +03:00

157 lines
6 KiB
Python

from __future__ import annotations
import os
import unittest
from types import SimpleNamespace
from unittest.mock import Mock, patch
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
from PyQt6.QtWidgets import QApplication
def _store(phase: str, running: bool = False):
return SimpleNamespace(snapshot=lambda: SimpleNamespace(launch_phase=phase, launch_running=running))
def _runtime(*, available: bool = True):
return SimpleNamespace(
start=Mock(return_value=True),
stop=Mock(return_value=True),
restart=Mock(return_value=True),
stop_and_exit=Mock(return_value=True),
is_available=Mock(return_value=available),
)
class LaunchControlTests(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls._app = QApplication.instance() or QApplication([])
def _control(self, phase: str, *, runtime=None, **kwargs):
from ui.launch_control import LaunchControl
runtime = runtime or _runtime()
return LaunchControl(runtime_feature=runtime, ui_state_store=_store(phase), **kwargs), runtime
def test_toggle_starts_or_stops_by_phase(self) -> None:
cases = {
"stopped": "start",
"failed": "start",
"running": "stop",
"starting": "stop",
"autostart_pending": "stop",
"stopping": "",
}
for phase, expected in cases.items():
with self.subTest(phase=phase):
control, runtime = self._control(phase)
self.assertEqual(control.toggle(), expected)
self.assertEqual(runtime.start.call_count, 1 if expected == "start" else 0)
self.assertEqual(runtime.stop.call_count, 1 if expected == "stop" else 0)
def test_unknown_phase_falls_back_to_running_flag(self) -> None:
from ui.launch_control import LaunchControl
runtime = _runtime()
store = SimpleNamespace(snapshot=lambda: SimpleNamespace(launch_phase="", launch_running=True))
control = LaunchControl(runtime_feature=runtime, ui_state_store=store)
self.assertEqual(control.phase(), "running")
self.assertEqual(control.toggle(), "stop")
def test_start_waits_for_conflicting_checks_before_runtime_start(self) -> None:
from ui import launch_control as module
conflicts = iter([True, True, False])
control, runtime = self._control("stopped", stop_conflicting_checks=lambda: next(conflicts))
scheduled: list[object] = []
with patch.object(module.QTimer, "singleShot", side_effect=lambda _ms, callback: scheduled.append(callback)):
control.start()
self.assertTrue(control.is_preparing())
self.assertIn("подбор стратегии", control.preparing_text())
runtime.start.assert_not_called()
scheduled.pop(0)()
scheduled.pop(0)()
runtime.start.assert_called_once_with()
self.assertFalse(control.is_preparing())
def test_toggle_is_ignored_while_preparing_and_stop_cancels_waiting(self) -> None:
from ui import launch_control as module
control, runtime = self._control("stopped", runtime=_runtime(available=False))
preparing: list[tuple[bool, str]] = []
control.preparingChanged.connect(lambda active, text: preparing.append((active, text)))
scheduled: list[object] = []
with patch.object(module.QTimer, "singleShot", side_effect=lambda _ms, callback: scheduled.append(callback)):
control.start()
self.assertEqual(control.toggle(), "")
control.stop()
scheduled.pop(0)()
runtime.start.assert_not_called()
runtime.stop.assert_called_once_with()
self.assertEqual(preparing, [(True, "Подготовка запуска..."), (False, "")])
def test_restart_starts_when_not_running(self) -> None:
control, runtime = self._control("stopped")
control.restart()
runtime.start.assert_called_once_with()
runtime.restart.assert_not_called()
control, runtime = self._control("running")
control.restart()
runtime.restart.assert_called_once_with()
runtime.start.assert_not_called()
def test_stop_and_exit_prefers_window_exit(self) -> None:
request_exit = Mock()
control, runtime = self._control("running", request_exit=request_exit)
control.stop_and_exit()
request_exit.assert_called_once_with(stop_dpi=True)
runtime.stop_and_exit.assert_not_called()
def test_phase_color_matches_status_card_colors(self) -> None:
from presets.ui.control import control_runtime
from ui.launch_control import phase_color
for phase in ("running", "starting", "stopping", "failed"):
with self.subTest(phase=phase):
plan = control_runtime.build_status_plan(state=phase, last_error="", language="ru")
self.assertEqual(phase_color(phase), plan.dot_color)
self.assertIsNone(phase_color("stopped"))
def test_control_page_mixin_delegates_to_launch_control(self) -> None:
from presets.ui.control.control_page_shared import ControlPageActionMixin
class Page(ControlPageActionMixin):
def __init__(self) -> None:
self._launch_control = Mock()
self.loading: list[tuple[bool, str]] = []
def set_loading(self, loading: bool, text: str = "") -> None:
self.loading.append((loading, text))
page = Page()
page._toggle_dpi()
page._start_dpi()
page._stop_dpi()
page._stop_and_exit()
page._on_launch_preparing_changed(True, "Подготовка запуска...")
page._launch_control.toggle.assert_called_once_with()
page._launch_control.start.assert_called_once_with()
page._launch_control.stop.assert_called_once_with()
page._launch_control.stop_and_exit.assert_called_once_with()
self.assertEqual(page.loading, [(True, "Подготовка запуска...")])
if __name__ == "__main__":
unittest.main()