zapretgui/tests/test_scrollbar_style_skip.py
loop-uh 8af806ca50 Не подменять QStyle ради уже постоянных скроллбаров
Подмена стиля приложения заново полирует каждый живой виджет и блокировала
GUI-поток примерно на 630 мс при каждом старте — это и был
StartupQtScrollStyle в логах. Нужна она только там, где скроллбары
исчезающие, а штатные Windows и Fusion и без подмены отвечают
SH_ScrollBar_Transient = 0. Теперь стиль сначала спрашивают и заменяют,
только когда это меняет поведение.

Заодно две метрики перестали вводить в заблуждение:

- StartupQtScrollStyle пишет, была подмена или пропуск;
- StartupSecondarySidebarReady делит wall-clock и собственную работу.
  Группы ставятся по одной через QTimer, поэтому 1464 мс в логе были
  ожиданием в очереди событий GUI, занятой стартом, а не построением
  сайдбара: оптимизировать там нечего, и метрика больше не притворяется,
  что есть.

Наблюдатель отзывчивости получил режим охоты за рывками. Штатный порог в
2 секунды ловит «программа не отвечает», но рывки интерфейса живут в
десятках миллисекунд и до сих пор не оставляли следов.
ZAPRET_UI_JITTER_MS=60 переводит наблюдателя на короткие задержки кадра
со стеком GUI-потока в обычном логе; порог файловых дампов остался
прежним, иначе каждый рывок писал бы отчёт в папку крэш-логов.

Тесты: test_scrollbar_style_skip — пропуск подмены, установка прокси при
исчезающих скроллбарах, работа без стиля; test_ui_jitter_watchdog_mode —
разбор переменной, параметры режима, формат сообщений в миллисекундах,
неизменность штатного режима.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 17:36:29 +03:00

64 lines
2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
import unittest
from unittest.mock import Mock
from PyQt6.QtWidgets import QApplication, QProxyStyle, QStyle, QStyleFactory
from main.qt_runtime import _install_non_transient_scrollbars_style
class _TransientScrollbarStyle(QProxyStyle):
"""Стиль с исчезающими скроллбарами — как на macOS."""
def styleHint(self, hint, option=None, widget=None, returnData=None): # noqa: N802 - Qt API
if hint == QStyle.StyleHint.SH_ScrollBar_Transient:
return 1
return super().styleHint(hint, option, widget, returnData)
class _FakeApp:
def __init__(self, style) -> None:
self._style = style
self.setStyle = Mock()
def style(self):
return self._style
class ScrollbarStyleSkipTests(unittest.TestCase):
"""Подмена QStyle заново полирует все виджеты — делать её вхолостую нельзя."""
@classmethod
def setUpClass(cls) -> None:
cls._app = QApplication.instance() or QApplication([])
def test_permanent_scrollbars_skip_style_replacement(self) -> None:
app = _FakeApp(QStyleFactory.create("Fusion"))
replaced = _install_non_transient_scrollbars_style(app)
self.assertFalse(replaced)
app.setStyle.assert_not_called()
def test_transient_scrollbars_get_the_proxy_style(self) -> None:
app = _FakeApp(_TransientScrollbarStyle(QStyleFactory.create("Fusion")))
replaced = _install_non_transient_scrollbars_style(app)
self.assertTrue(replaced)
app.setStyle.assert_called_once()
proxy = app.setStyle.call_args.args[0]
self.assertEqual(proxy.styleHint(QStyle.StyleHint.SH_ScrollBar_Transient), 0)
def test_missing_style_still_installs_proxy(self) -> None:
app = _FakeApp(None)
replaced = _install_non_transient_scrollbars_style(app)
self.assertTrue(replaced)
app.setStyle.assert_called_once()
if __name__ == "__main__":
unittest.main()