Продолжение блочной сборки (8bfc2dd3).
- Стили qfluentwidgets: у виджета со стилем больше нет двух перехватчиков
событий библиотеки (ждали смены «своего стиля» и «ленивой» смены темы).
Это было два вызова функции на Python на каждое событие каждого такого
виджета. «Свой стиль» применяется там, где его задают; смена темы
обновляет стили сразу (ленивой сменой программа не пользовалась).
- Закрытие выпадающих списков при потере активности слушает один сигнал
приложения вместо перехватчика, через который шло каждое событие каждого
объекта.
- Имя и состояние элемента для экранного диктора: служебные дочерние кнопки
убираются из Tab-порядка за один обход детей вместо четырёх (на главной
335 обходов дерева при сборке → 91).
- «Оформление»: новогоднее оформление, прозрачность, акцентный цвет и
производительность — блок ниже первого экрана (ui.block_build). При
создании страницы строится 52 элемента вместо 135. Блок, собранный позже,
перечитывает настройки: прозрачность меняют и из меню в трее.
- У заглушки блока и слоя появления убрана лишняя установка фокуса
(значение по умолчанию), из-за неё оба файла попадали под проверку
доступности.
Замер на хосте против 21.1.7.85, семь страниц: сборка 255 → 171 мс, показ
75 → 54 мс, открытие рядом с занятым фоновым потоком 5644 → 3478 мс
(«Оформление» 989 → 125 мс).
Тест test_appearance_page_builds_all_sections_in_initial_pass заменён:
решение владельца от 2026-10-08 — не строить сразу то, чего не видно.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
58 lines
2.3 KiB
Python
58 lines
2.3 KiB
Python
"""Выпадающие списки закрываются, когда программа теряет активность."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
|
|
|
from PyQt6.QtCore import QEvent, Qt
|
|
from PyQt6.QtWidgets import QApplication, QLabel
|
|
|
|
from ui.combo_popup_guard import GlobalComboPopupCloser
|
|
|
|
|
|
class ComboPopupGuardTests(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls) -> None:
|
|
cls._app = QApplication.instance() or QApplication([])
|
|
|
|
def _closer(self) -> GlobalComboPopupCloser:
|
|
closer = GlobalComboPopupCloser(self._app)
|
|
self.addCleanup(closer.deleteLater)
|
|
self.addCleanup(closer.cleanup)
|
|
return closer
|
|
|
|
def test_popups_close_when_the_application_becomes_inactive(self) -> None:
|
|
closer = self._closer()
|
|
with mock.patch.object(closer, "close_all_popups") as close:
|
|
self._app.applicationStateChanged.emit(Qt.ApplicationState.ApplicationInactive)
|
|
close.assert_called_once()
|
|
close.reset_mock()
|
|
self._app.applicationStateChanged.emit(Qt.ApplicationState.ApplicationActive)
|
|
close.assert_not_called()
|
|
|
|
def test_closer_does_not_watch_every_event_of_the_application(self) -> None:
|
|
# Раньше это был перехватчик на всё приложение: через функцию на Python
|
|
# проходило каждое событие каждого объекта.
|
|
closer = self._closer()
|
|
self.assertNotIn("eventFilter", vars(GlobalComboPopupCloser))
|
|
with mock.patch.object(closer, "close_all_popups") as close:
|
|
label = QLabel("текст")
|
|
self.addCleanup(label.deleteLater)
|
|
QApplication.sendEvent(label, QEvent(QEvent.Type.ApplicationDeactivate))
|
|
close.assert_not_called()
|
|
|
|
def test_cleanup_stops_listening(self) -> None:
|
|
closer = GlobalComboPopupCloser(self._app)
|
|
self.addCleanup(closer.deleteLater)
|
|
closer.cleanup()
|
|
with mock.patch.object(closer, "close_all_popups") as close:
|
|
self._app.applicationStateChanged.emit(Qt.ApplicationState.ApplicationInactive)
|
|
close.assert_not_called()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|