zapretgui/tests/test_profile_setup_accessibility.py
loop-uh 174fbd70e2
All checks were successful
Zapret source guards / validate (push) Successful in 10s
Windows-сборка / Dev Dev 21.1.7.194 опубликован
Ссылки на git и вики: открытое имя сайта выбирается один раз и для обновления, и для кнопок
Основные имена git.zapret.moe и wiki.zapret.moe режут по TLS. Теперь общий модуль
utils/site_links (на ядре zapret_update/mirrors, семейства git и wiki) выбирает первое
ответившее имя и запоминает его; им пользуются проверка выпуска, очередь обновлений,
хранилище файлов и все кнопки со ссылками (Справка, Исходный код, Документация, заявки,
тур). Адреса пишутся один раз в config/urls.py на основных именах.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 06:17:57 -04:00

266 lines
10 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.

import os
import unittest
from types import SimpleNamespace
from unittest.mock import Mock, patch
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QApplication
from profile.ui.preset_setup_page import PresetSetupPageBase
from profile.ui.profile_setup_page import ProfileSetupPageBase
def _worker_stub(*_args, **_kwargs):
return None
class _DialogButton:
def __init__(self, text: str = "", parent=None) -> None:
self._text = ""
if text:
self._text = str(text)
self.parent = parent
self._accessible_name = ""
self._accessible_description = ""
self._properties: dict[str, object] = {}
self.hidden = False
self.clicked = _Signal()
def hide(self) -> None:
self.hidden = True
def setText(self, text: str) -> None: # noqa: N802
self._text = str(text)
def text(self) -> str:
return self._text
def click(self) -> None:
self.clicked.emit()
def accessibleName(self) -> str: # noqa: N802
return self._accessible_name
def setAccessibleName(self, text: str) -> None: # noqa: N802
self._accessible_name = str(text)
def accessibleDescription(self) -> str: # noqa: N802
return self._accessible_description
def setAccessibleDescription(self, text: str) -> None: # noqa: N802
self._accessible_description = str(text)
def property(self, name: str): # noqa: A003
return self._properties.get(name)
def setProperty(self, name: str, value) -> None: # noqa: N802
self._properties[name] = value
class _Signal:
def __init__(self) -> None:
self._callbacks = []
def connect(self, callback) -> None:
self._callbacks.append(callback)
def emit(self) -> None:
for callback in list(self._callbacks):
callback()
class _ButtonLayout:
def __init__(self) -> None:
self.widgets = []
def insertWidget(self, index: int, widget) -> None: # noqa: N802
self.widgets.insert(index, widget)
class _MessageBox:
instances: list["_MessageBox"] = []
def __init__(self, title: str, body: str, parent=None) -> None:
self.title = title
self.body = body
self.parent = parent
self.yesButton = _DialogButton()
self.cancelButton = _DialogButton()
self.buttonLayout = _ButtonLayout()
self.exec_called = False
_MessageBox.instances.append(self)
def exec(self) -> bool:
self.exec_called = True
return False
class ProfileSetupAccessibilityTests(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.app = QApplication.instance() or QApplication([])
def tearDown(self) -> None:
self.app.closeAllWindows()
self.app.processEvents()
def _make_page(self) -> ProfileSetupPageBase:
return ProfileSetupPageBase(
create_profile_setup_load_worker=_worker_stub,
create_profile_list_file_load_worker=_worker_stub,
create_profile_list_file_save_worker=_worker_stub,
create_profile_list_file_validation_worker=_worker_stub,
create_profile_settings_save_worker=_worker_stub,
create_profile_raw_text_save_worker=_worker_stub,
create_profile_enabled_save_worker=_worker_stub,
create_profile_user_update_worker=_worker_stub,
create_profile_user_delete_worker=_worker_stub,
create_profile_strategy_apply_worker=_worker_stub,
create_profile_strategy_feedback_save_worker=_worker_stub,
open_profiles=lambda: None,
open_root=lambda: None,
on_profile_changed=lambda: None,
)
def test_settings_line_edit_buttons_do_not_take_tab_focus(self) -> None:
page = self._make_page()
self.addCleanup(page.deleteLater)
for line_edit in (page._filter_value, page._in_range_value, page._out_range_value):
line_edit.setText("8")
buttons = [
child
for child in line_edit.findChildren(object)
if str(getattr(child, "objectName", lambda: "")() or "") == "lineEditButton"
and hasattr(child, "setFocusPolicy")
]
self.assertTrue(buttons)
self.assertTrue(all(button.focusPolicy() == Qt.FocusPolicy.NoFocus for button in buttons))
def test_range_mode_combo_options_are_named_for_screen_reader(self) -> None:
page = self._make_page()
self.addCleanup(page.deleteLater)
menu = page._in_range_mode._create_accessible_combo_menu()
self.assertEqual(
menu.view.item(0).data(Qt.ItemDataRole.AccessibleTextRole),
"Режим in-range: a — всегда, выбран",
)
self.assertEqual(
menu.view.item(1).data(Qt.ItemDataRole.AccessibleTextRole),
"Режим in-range: x — никогда, не выбран",
)
def test_filter_kind_combo_options_are_named_for_screen_reader(self) -> None:
page = self._make_page()
self.addCleanup(page.deleteLater)
create_menu = getattr(page._filter_combo, "_create_accessible_combo_menu", None)
self.assertIsNotNone(create_menu)
menu = create_menu()
self.assertEqual(
menu.view.item(0).data(Qt.ItemDataRole.AccessibleTextRole),
"Тип списка profile: Hostlist, выбран",
)
self.assertEqual(
menu.view.item(1).data(Qt.ItemDataRole.AccessibleTextRole),
"Тип списка profile: IPset, не выбран",
)
def test_delete_user_profile_dialog_buttons_are_named_for_screen_reader(self) -> None:
page = ProfileSetupPageBase.__new__(ProfileSetupPageBase)
page._profile_key = "template:user:user-1"
page._payload = SimpleNamespace(item=SimpleNamespace(user_profile_id=""))
page._request_user_profile_delete = Mock()
_MessageBox.instances = []
with patch("profile.ui.profile_setup_page.MessageBox", _MessageBox):
ProfileSetupPageBase._on_delete_user_profile_clicked(page)
dialog = _MessageBox.instances[0]
self.assertEqual(dialog.yesButton.accessibleName(), "Удалить пользовательский profile")
self.assertIn("будет удалён из библиотеки", dialog.yesButton.accessibleDescription())
self.assertEqual(dialog.cancelButton.accessibleName(), "Отменить удаление пользовательского profile")
self.assertTrue(dialog.exec_called)
page._request_user_profile_delete.assert_not_called()
def test_delete_profile_from_preset_dialog_buttons_are_named_for_screen_reader(self) -> None:
page = PresetSetupPageBase.__new__(PresetSetupPageBase)
page._request_profile_context_action = Mock()
_MessageBox.instances = []
with patch("profile.ui.preset_setup_page.MessageBox", _MessageBox):
PresetSetupPageBase._delete_profile_from_menu(page, "profile-1")
dialog = _MessageBox.instances[0]
self.assertEqual(dialog.yesButton.accessibleName(), "Удалить profile из текущего preset")
self.assertIn("только из текущего preset", dialog.yesButton.accessibleDescription())
self.assertEqual(dialog.cancelButton.accessibleName(), "Отменить удаление profile из preset")
self.assertTrue(dialog.exec_called)
page._request_profile_context_action.assert_not_called()
def test_delete_user_profile_from_preset_dialog_buttons_are_named_for_screen_reader(self) -> None:
page = PresetSetupPageBase.__new__(PresetSetupPageBase)
page._profiles_list = SimpleNamespace(
profile_item_for_key=lambda _profile_key: SimpleNamespace(user_profile_id="")
)
page._request_user_profile_delete = Mock()
_MessageBox.instances = []
with patch("profile.ui.preset_setup_page.MessageBox", _MessageBox):
PresetSetupPageBase._delete_user_profile_from_menu(page, "template:user:user-1")
dialog = _MessageBox.instances[0]
self.assertEqual(dialog.yesButton.accessibleName(), "Удалить пользовательский profile")
self.assertIn("будет удалён из библиотеки", dialog.yesButton.accessibleDescription())
self.assertEqual(dialog.cancelButton.accessibleName(), "Отменить удаление пользовательского profile")
self.assertTrue(dialog.exec_called)
page._request_user_profile_delete.assert_not_called()
def test_preset_profile_info_dialog_close_button_is_named_for_screen_reader(self) -> None:
page = PresetSetupPageBase.__new__(PresetSetupPageBase)
_MessageBox.instances = []
with patch("profile.ui.preset_setup_page.MessageBox", _MessageBox):
PresetSetupPageBase._show_profile_info(page)
dialog = _MessageBox.instances[0]
self.assertEqual(dialog.yesButton.accessibleName(), "Закрыть справку о настройке пресета")
self.assertIn("Закрывает справку", dialog.yesButton.accessibleDescription())
self.assertTrue(dialog.cancelButton.hidden)
self.assertTrue(dialog.exec_called)
def test_preset_profile_info_dialog_has_profile_site_button(self) -> None:
page = PresetSetupPageBase.__new__(PresetSetupPageBase)
_MessageBox.instances = []
opened_urls = []
with (
patch("profile.ui.preset_setup_page.MessageBox", _MessageBox),
patch("profile.ui.preset_setup_page.PushButton", _DialogButton, create=True),
patch(
"utils.site_links.open_in_browser",
side_effect=lambda url: opened_urls.append(url),
),
):
PresetSetupPageBase._show_profile_info(page)
_MessageBox.instances[0].buttonLayout.widgets[0].click()
dialog = _MessageBox.instances[0]
self.assertEqual(len(dialog.buttonLayout.widgets), 1)
site_button = dialog.buttonLayout.widgets[0]
self.assertEqual(site_button.text(), "Открыть сайт с профилями")
self.assertEqual(site_button.accessibleName(), "Открыть сайт с профилями")
self.assertEqual(opened_urls, ["https://wiki2.zapret.moe/Zapret2/profile"])
if __name__ == "__main__":
unittest.main()