zapretgui/tests/test_user_presets_accessibility.py
loop-uh 06dcc16710
All checks were successful
Zapret source guards / validate (push) Successful in 4s
Перевести ссылки документации на wiki.zapret.moe
Obsidian Publish (publish.obsidian.md/zapret) закрыт, вики переехала на
self-hosted Quartz: https://wiki.zapret.moe. Пути заметок сохранены,
префикс Privacy/ из старых URL убран.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 16:18:27 +03:00

770 lines
34 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 os
import tempfile
import unittest
from types import SimpleNamespace
from unittest.mock import patch
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
from PyQt6.QtCore import Qt, QUrl
from PyQt6.QtGui import QKeyEvent
from PyQt6.QtTest import QTest
from PyQt6.QtWidgets import QApplication, QAbstractItemView, QVBoxLayout, QWidget
from qfluentwidgets import FluentIcon, LineEdit, PrimaryToolButton, StrongBodyLabel
from app.ui_texts import tr
from presets.ui.common.user_presets_build import build_user_presets_page_shell
from presets.ui.common.user_presets_dialogs import CreatePresetDialog, RenamePresetDialog, ResetAllPresetsDialog
from presets.ui.common.user_presets_page import UserPresetsPageBase
from presets.ui.common.user_presets_page_lifecycle import apply_user_presets_language
from ui.theme import get_cached_qta_pixmap, get_theme_tokens
class _PageHost(QWidget):
def __init__(self) -> None:
super().__init__()
self.resize(1000, 600)
self.layout = QVBoxLayout(self)
def viewport(self): # noqa: ANN201
return self
class _DialogButton:
def __init__(self, text: str = "", parent=None) -> None:
self._text = 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 text(self) -> str:
return self._text
def setText(self, text: str) -> None: # noqa: N802
self._text = str(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 True
class UserPresetsAccessibilityTests(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls._app = QApplication.instance() or QApplication([])
def _build_widgets(self, *, on_preset_list_action=None):
parent = _PageHost()
widgets = build_user_presets_page_shell(
parent=parent,
tr_fn=lambda _key, default: default,
tokens=get_theme_tokens(),
strong_body_label_cls=StrongBodyLabel,
line_edit_cls=LineEdit,
primary_tool_button_cls=PrimaryToolButton,
tr_prefix="page.user_presets",
delegate_language_scope="user_presets",
delegate_help_name_role="helpName",
fluent_icon=FluentIcon,
get_cached_qta_pixmap_fn=get_cached_qta_pixmap,
on_open_new_configs_post=lambda: None,
on_create_clicked=lambda: None,
on_import_clicked=lambda: None,
on_open_folder_clicked=lambda: None,
on_reset_all_presets_clicked=lambda: None,
on_open_presets_info=lambda: None,
on_info_clicked=lambda: None,
on_preset_search_text_changed=lambda _text: None,
on_activate_preset=lambda _index: None,
on_move_preset_by_step=lambda *_args: None,
on_item_dropped=lambda *_args: None,
on_preset_context_requested=lambda *_args: None,
on_folder_context_requested=lambda *_args: None,
on_background_context_requested=lambda *_args: None,
on_preset_list_action=on_preset_list_action or (lambda *_args: None),
ui_language="ru",
)
return parent, widgets
def _dialog_parent(self) -> QWidget:
parent = QWidget()
parent.resize(640, 480)
parent.show()
self.addCleanup(parent.deleteLater)
return parent
def test_toolbar_controls_have_screen_reader_text(self) -> None:
_parent, widgets = self._build_widgets()
self._assert_accessibility(widgets)
def test_language_refresh_keeps_screen_reader_text(self) -> None:
parent, widgets = self._build_widgets()
apply_user_presets_language(
tr_fn=lambda _key, default: default,
configs_title_label=widgets.configs_title_label,
get_configs_btn=widgets.get_configs_btn,
create_btn=widgets.create_btn,
import_btn=widgets.import_btn,
open_folder_btn=widgets.open_folder_btn,
reset_all_btn=widgets.reset_all_btn,
presets_info_btn=widgets.presets_info_btn,
info_btn=widgets.info_btn,
preset_search_input=widgets.preset_search_input,
presets_list=widgets.presets_list,
presets_delegate=widgets.presets_delegate,
ui_language="ru",
viewport=parent.viewport(),
layout=parent.layout,
toolbar_layout=widgets.toolbar_layout,
refresh_presets_view_from_cache_fn=lambda: None,
apply_mode_labels_fn=lambda: None,
tr_prefix="page.user_presets",
)
self._assert_accessibility(widgets)
def test_preset_list_opens_selected_preset_menu_from_keyboard(self) -> None:
from ui.presets_menu.model import PresetListModel
from ui.presets_menu.view import LinkedWheelListView
model = PresetListModel()
model.set_rows(
[
{
"kind": "preset",
"name": "Default",
"file_name": "Default.txt",
"folder_key": "common",
}
]
)
view = LinkedWheelListView()
self.addCleanup(view.deleteLater)
view.setModel(model)
view.setCurrentIndex(model.index(0, 0))
requested: list[str] = []
view.preset_context_requested.connect(lambda name, _point: requested.append(name))
event = QKeyEvent(QKeyEvent.Type.KeyPress, int(Qt.Key.Key_Menu), Qt.KeyboardModifier.NoModifier)
view.keyPressEvent(event)
self.assertTrue(event.isAccepted())
self.assertEqual(requested, ["Default.txt"])
def test_preset_list_toggles_selected_folder_from_keyboard(self) -> None:
from ui.presets_menu.model import PresetListModel
from ui.presets_menu.view import LinkedWheelListView
model = PresetListModel()
model.set_rows(
[
{
"kind": "folder",
"name": "Игры",
"folder_key": "games",
"count": 2,
"is_collapsed": True,
}
]
)
view = LinkedWheelListView()
self.addCleanup(view.deleteLater)
view.setModel(model)
view.setCurrentIndex(model.index(0, 0))
requested: list[str] = []
view.folder_toggle_requested.connect(requested.append)
event = QKeyEvent(QKeyEvent.Type.KeyPress, int(Qt.Key.Key_Return), Qt.KeyboardModifier.NoModifier)
view.keyPressEvent(event)
self.assertTrue(event.isAccepted())
self.assertEqual(requested, ["games"])
def test_plain_arrows_select_presets_and_skip_folder_rows(self) -> None:
from ui.presets_menu.model import PresetListModel
from ui.presets_menu.view import LinkedWheelListView
model = PresetListModel()
model.set_rows(
[
{"kind": "folder", "name": "Общие", "folder_key": "common"},
{"kind": "preset", "name": "Первый", "file_name": "First.txt", "folder_key": "common"},
{"kind": "folder", "name": "Игры", "folder_key": "games"},
{"kind": "preset", "name": "Второй", "file_name": "Second.txt", "folder_key": "games"},
]
)
view = LinkedWheelListView()
self.addCleanup(view.deleteLater)
view.setModel(model)
view.setCurrentIndex(model.index(1, 0))
moved: list[tuple[str, int]] = []
view.preset_move_requested.connect(lambda name, direction: moved.append((name, direction)))
down = QKeyEvent(QKeyEvent.Type.KeyPress, int(Qt.Key.Key_Down), Qt.KeyboardModifier.NoModifier)
view.keyPressEvent(down)
self.assertTrue(down.isAccepted())
self.assertEqual(view.currentIndex().row(), 3)
up = QKeyEvent(QKeyEvent.Type.KeyPress, int(Qt.Key.Key_Up), Qt.KeyboardModifier.NoModifier)
view.keyPressEvent(up)
self.assertTrue(up.isAccepted())
self.assertEqual(view.currentIndex().row(), 1)
self.assertEqual(moved, [])
def test_ctrl_arrows_request_preset_reordering_without_changing_selection(self) -> None:
from ui.presets_menu.model import PresetListModel
from ui.presets_menu.view import LinkedWheelListView
model = PresetListModel()
model.set_rows(
[
{"kind": "preset", "name": "Первый", "file_name": "First.txt", "folder_key": "common"},
{"kind": "preset", "name": "Второй", "file_name": "Second.txt", "folder_key": "common"},
]
)
view = LinkedWheelListView()
self.addCleanup(view.deleteLater)
view.setModel(model)
view.setCurrentIndex(model.index(0, 0))
moved: list[tuple[str, int]] = []
view.preset_move_requested.connect(lambda name, direction: moved.append((name, direction)))
down = QKeyEvent(
QKeyEvent.Type.KeyPress,
int(Qt.Key.Key_Down),
Qt.KeyboardModifier.ControlModifier,
)
view.keyPressEvent(down)
self.assertTrue(down.isAccepted())
self.assertEqual(moved, [("First.txt", 1)])
self.assertEqual(view.currentIndex().row(), 0)
def test_preset_list_activates_selected_preset_with_space(self) -> None:
from ui.presets_menu.model import PresetListModel
from ui.presets_menu.view import LinkedWheelListView
model = PresetListModel()
model.set_rows(
[
{
"kind": "preset",
"name": "Default",
"file_name": "Default.txt",
}
]
)
view = LinkedWheelListView()
self.addCleanup(view.deleteLater)
view.setModel(model)
view.setCurrentIndex(model.index(0, 0))
requested: list[str] = []
view.preset_activated.connect(requested.append)
event = QKeyEvent(QKeyEvent.Type.KeyPress, int(Qt.Key.Key_Space), Qt.KeyboardModifier.NoModifier)
view.keyPressEvent(event)
self.assertTrue(event.isAccepted())
self.assertEqual(requested, ["Default.txt"])
def test_preset_search_enter_activates_current_preset(self) -> None:
parent, widgets = self._build_widgets()
self.addCleanup(parent.deleteLater)
widgets.presets_model.set_rows(
[
{
"kind": "preset",
"name": "Default",
"file_name": "Default.txt",
}
]
)
widgets.presets_list.setCurrentIndex(widgets.presets_model.index(0, 0))
requested: list[str] = []
widgets.presets_list.preset_activated.connect(requested.append)
parent.show()
self._app.processEvents()
widgets.preset_search_input.setFocus()
self._app.processEvents()
QTest.keyClick(widgets.preset_search_input, Qt.Key.Key_Return)
self._app.processEvents()
self.assertIs(self._app.focusWidget(), widgets.presets_list)
self.assertEqual(requested, ["Default.txt"])
def test_preset_search_arrow_down_moves_focus_to_preset_list(self) -> None:
parent, widgets = self._build_widgets()
self.addCleanup(parent.deleteLater)
widgets.presets_model.set_rows(
[
{
"kind": "preset",
"name": "Default",
"file_name": "Default.txt",
},
{
"kind": "preset",
"name": "Gaming",
"file_name": "Gaming.txt",
},
]
)
widgets.presets_list.setCurrentIndex(widgets.presets_model.index(0, 0))
parent.show()
self._app.processEvents()
widgets.preset_search_input.setFocus()
self._app.processEvents()
QTest.keyClick(widgets.preset_search_input, Qt.Key.Key_Down)
self._app.processEvents()
self.assertIs(self._app.focusWidget(), widgets.presets_list)
self.assertEqual(widgets.presets_list.currentIndex().row(), 1)
def test_ctrl_f_focuses_always_visible_preset_search(self) -> None:
from app.state_store import MainWindowStateStore
from presets.ui.common.user_presets_page_runtime import UserPresetsRuntimeActions
from presets.ui.zapret2.user_presets_page import Zapret2UserPresetsPage
presets_dir = tempfile.TemporaryDirectory()
self.addCleanup(presets_dir.cleanup)
noop = lambda *args, **kwargs: None
actions = UserPresetsRuntimeActions(
get_selected_source_preset_file_name=lambda *_args: "",
list_preset_manifests=lambda *_args: [],
get_user_presets_dir=lambda *_args: presets_dir.name,
get_cached_preset_list_metadata=lambda *_args: {},
warm_preset_list_metadata_cache=lambda *_args: {},
get_preset_source_path_by_file_name=lambda *_args: "",
preset_differs_from_builtin_by_file_name=lambda *_args: False,
read_single_preset_list_metadata=lambda *_args: None,
)
page = Zapret2UserPresetsPage(
preset_runtime_actions=actions,
connect_preset_signals=noop,
create_user_presets_open_folder_worker=noop,
create_preset_edit_action_worker=noop,
create_preset_bulk_action_worker=noop,
create_preset_activate_worker=noop,
create_preset_item_action_worker=noop,
create_preset_link_action_worker=noop,
create_preset_folder_action_worker=noop,
create_preset_storage_action_worker=noop,
load_preset_folder_state=lambda *_args: {},
open_preset_raw_editor=noop,
ui_state_store=MainWindowStateStore(),
)
self.addCleanup(page.deleteLater)
self.addCleanup(page.cleanup)
page.resize(1000, 700)
page.show()
self._app.processEvents()
self.assertFalse(page._preset_search_input.isHidden())
QTest.keyClick(page.presets_list, Qt.Key.Key_F, Qt.KeyboardModifier.ControlModifier)
self._app.processEvents()
self.assertFalse(page._preset_search_input.isHidden())
self.assertIs(self._app.focusWidget(), page._preset_search_input)
page._preset_search_input.setText("Gaming")
page.presets_list.setFocus()
self._app.processEvents()
QTest.keyClick(page.presets_list, Qt.Key.Key_F, Qt.KeyboardModifier.ControlModifier)
self._app.processEvents()
self.assertFalse(page._preset_search_input.isHidden())
self.assertEqual(page._preset_search_input.text(), "Gaming")
self.assertEqual(page._preset_search_input.selectedText(), "Gaming")
self.assertIs(self._app.focusWidget(), page._preset_search_input)
def test_preset_list_navigation_does_not_use_native_selection_state(self) -> None:
parent, widgets = self._build_widgets()
self.addCleanup(parent.deleteLater)
widgets.presets_model.set_rows(
[
{
"kind": "preset",
"name": "Default",
"file_name": "Default.txt",
},
{
"kind": "preset",
"name": "Gaming",
"file_name": "Gaming.txt",
},
]
)
widgets.presets_list.setCurrentIndex(widgets.presets_model.index(0, 0))
event = QKeyEvent(QKeyEvent.Type.KeyPress, int(Qt.Key.Key_Down), Qt.KeyboardModifier.NoModifier)
widgets.presets_list.keyPressEvent(event)
self.assertTrue(event.isAccepted())
self.assertEqual(widgets.presets_list.selectionMode(), QAbstractItemView.SelectionMode.NoSelection)
self.assertEqual(widgets.presets_list.currentIndex().row(), 1)
self.assertEqual(widgets.presets_list.selectedIndexes(), [])
self.assertIn(
"Список пользовательских пресетов: Gaming",
widgets.presets_list.property("screenReaderStateText"),
)
def test_preset_list_toggles_selected_folder_with_space(self) -> None:
from ui.presets_menu.model import PresetListModel
from ui.presets_menu.view import LinkedWheelListView
model = PresetListModel()
model.set_rows(
[
{
"kind": "folder",
"name": "Игры",
"folder_key": "games",
"count": 2,
"is_collapsed": True,
}
]
)
view = LinkedWheelListView()
self.addCleanup(view.deleteLater)
view.setModel(model)
view.setCurrentIndex(model.index(0, 0))
requested: list[str] = []
view.folder_toggle_requested.connect(requested.append)
event = QKeyEvent(QKeyEvent.Type.KeyPress, int(Qt.Key.Key_Space), Qt.KeyboardModifier.NoModifier)
view.keyPressEvent(event)
self.assertTrue(event.isAccepted())
self.assertEqual(requested, ["games"])
def test_preset_list_updates_screen_reader_text_when_current_row_changes(self) -> None:
from ui.presets_menu.model import PresetListModel
from ui.presets_menu.view import LinkedWheelListView
model = PresetListModel()
model.set_rows(
[
{
"kind": "preset",
"name": "Default",
"file_name": "Default.txt",
"is_active": True,
"is_builtin": True,
"folder_name": "Общие",
"is_pinned": True,
"rating": 9,
},
{
"kind": "folder",
"name": "Игры",
"folder_key": "games",
"count": 2,
"is_collapsed": True,
},
]
)
view = LinkedWheelListView()
self.addCleanup(view.deleteLater)
view.set_screen_reader_list_name("Список пользовательских пресетов")
view.setModel(model)
view.setCurrentIndex(model.index(0, 0))
self.assertEqual(
view.property("screenReaderStateText"),
"Список пользовательских пресетов: Default, активный пресет, встроенный, "
"папка: Общие, закреплённый, оценка 9. Нажмите Enter или Пробел, чтобы открыть preset.",
)
view.setCurrentIndex(model.index(1, 0))
self.assertEqual(
view.property("screenReaderStateText"),
"Список пользовательских пресетов: Папка Игры, 2 пресета, свернута. "
"Нажмите Enter или Пробел, чтобы свернуть или развернуть папку.",
)
def test_preset_page_wires_keyboard_folder_toggle_to_action_handler(self) -> None:
requested: list[tuple[str, str]] = []
_parent, widgets = self._build_widgets(on_preset_list_action=lambda action, name: requested.append((action, name)))
widgets.presets_model.set_rows(
[
{
"kind": "folder",
"name": "Игры",
"folder_key": "games",
"count": 2,
"is_collapsed": True,
}
]
)
widgets.presets_list.setCurrentIndex(widgets.presets_model.index(0, 0))
event = QKeyEvent(QKeyEvent.Type.KeyPress, int(Qt.Key.Key_Return), Qt.KeyboardModifier.NoModifier)
widgets.presets_list.keyPressEvent(event)
self.assertTrue(event.isAccepted())
self.assertEqual(requested, [("toggle_folder", "games")])
def test_info_dialog_close_button_has_screen_reader_text(self) -> None:
page = UserPresetsPageBase.__new__(UserPresetsPageBase)
page._config = SimpleNamespace(tr_prefix="page.user_presets")
page._tr = lambda _key, default, **kwargs: default.format(**kwargs) if kwargs else default
page.window = lambda: None
_MessageBox.instances = []
with patch("presets.ui.common.user_presets_page.MessageBox", _MessageBox):
UserPresetsPageBase._on_info_clicked(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_info_dialog_has_preset_site_button_instead_of_plain_link(self) -> None:
page = UserPresetsPageBase.__new__(UserPresetsPageBase)
page._config = SimpleNamespace(tr_prefix="page.user_presets")
page._tr = lambda _key, default, **kwargs: default.format(**kwargs) if kwargs else default
page.window = lambda: None
_MessageBox.instances = []
opened_urls = []
with (
patch("presets.ui.common.user_presets_page.MessageBox", _MessageBox),
patch("presets.ui.common.user_presets_page.PushButton", _DialogButton, create=True),
patch(
"presets.ui.common.user_presets_page.QDesktopServices.openUrl",
side_effect=lambda url: opened_urls.append(url),
),
):
UserPresetsPageBase._on_info_clicked(page)
_MessageBox.instances[0].buttonLayout.widgets[0].click()
dialog = _MessageBox.instances[0]
self.assertNotIn("https://wiki.zapret.moe/Zapret2/preset", dialog.body)
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, [QUrl("https://wiki.zapret.moe/Zapret2/preset")])
def test_create_preset_dialog_has_screen_reader_text(self) -> None:
dialog = CreatePresetDialog([], self._dialog_parent())
self.addCleanup(dialog.deleteLater)
self.assertEqual(dialog.nameEdit.accessibleName(), "Название нового пресета")
self.assertIn("Например Игры", dialog.nameEdit.accessibleDescription())
if hasattr(dialog, "_source_seg"):
self.assertEqual(dialog._source_seg.accessibleName(), "Основа нового пресета, выбрано: Текущий пресет")
self.assertEqual(
dialog._source_seg.property("screenReaderStateText"),
"Основа нового пресета, выбрано: Текущий пресет",
)
self.assertIn("Выберите", dialog._source_seg.accessibleDescription())
current_item = dialog._source_seg.items["current"]
standard_item = dialog._source_seg.items["standard"]
self.assertEqual(
current_item.accessibleName(),
"Основа нового пресета: Текущий пресет, выбрано",
)
self.assertEqual(
standard_item.accessibleName(),
"Основа нового пресета: Встроенный пресет, не выбрано",
)
dialog._source_seg.setCurrentItem("standard")
self.assertEqual(dialog._source_seg.accessibleName(), "Основа нового пресета, выбрано: Встроенный пресет")
self.assertEqual(
dialog._source_seg.property("screenReaderStateText"),
"Основа нового пресета, выбрано: Встроенный пресет",
)
self.assertEqual(
current_item.accessibleName(),
"Основа нового пресета: Текущий пресет, не выбрано",
)
self.assertEqual(
standard_item.accessibleName(),
"Основа нового пресета: Встроенный пресет, выбрано",
)
self.assertEqual(dialog.yesButton.accessibleName(), "Создать пресет")
self.assertEqual(dialog.yesButton.property("screenReaderStateText"), "Создать пресет")
self.assertIn("Сохраняет текущие настройки", dialog.yesButton.accessibleDescription())
self.assertEqual(dialog.cancelButton.accessibleName(), "Отменить создание пресета")
self.assertEqual(dialog.cancelButton.property("screenReaderStateText"), "Отменить создание пресета")
self.assertFalse(dialog.validate())
self.assertEqual(dialog.warningLabel.accessibleName(), "Ошибка: Введите название.")
self.assertEqual(dialog.warningLabel.property("screenReaderStateText"), "Ошибка: Введите название.")
def test_rename_preset_dialog_has_screen_reader_text(self) -> None:
dialog = RenamePresetDialog("Дом", ["Дом"], self._dialog_parent())
self.addCleanup(dialog.deleteLater)
self.assertEqual(dialog.nameEdit.accessibleName(), "Новое название пресета")
self.assertIn("Текущее имя: Дом", dialog.nameEdit.accessibleDescription())
self.assertEqual(dialog.yesButton.accessibleName(), "Переименовать пресет")
self.assertEqual(dialog.yesButton.property("screenReaderStateText"), "Переименовать пресет")
self.assertIn("Меняет имя пресета", dialog.yesButton.accessibleDescription())
self.assertEqual(dialog.cancelButton.accessibleName(), "Отменить переименование пресета")
self.assertEqual(dialog.cancelButton.property("screenReaderStateText"), "Отменить переименование пресета")
def test_preset_name_dialog_clear_buttons_do_not_take_tab_focus(self) -> None:
dialogs = [
CreatePresetDialog([], self._dialog_parent()),
RenamePresetDialog("Дом", ["Дом"], self._dialog_parent()),
]
for dialog in dialogs:
self.addCleanup(dialog.deleteLater)
dialog.nameEdit.setText("Дом")
dialog.show()
self._app.processEvents()
for dialog in dialogs:
with self.subTest(name=dialog.nameEdit.accessibleName()):
buttons = [
child
for child in dialog.nameEdit.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_reset_presets_dialog_has_screen_reader_text(self) -> None:
dialog = ResetAllPresetsDialog(self._dialog_parent())
self.addCleanup(dialog.deleteLater)
self.assertEqual(dialog.yesButton.accessibleName(), "Вернуть встроенные пресеты")
self.assertEqual(dialog.yesButton.property("screenReaderStateText"), "Вернуть встроенные пресеты")
self.assertIn("Изменения во встроенных пресетах будут потеряны", dialog.yesButton.accessibleDescription())
self.assertEqual(dialog.cancelButton.accessibleName(), "Отменить возврат встроенных пресетов")
self.assertEqual(
dialog.cancelButton.property("screenReaderStateText"),
"Отменить возврат встроенных пресетов",
)
def test_user_preset_help_text_explains_preset_file_and_wiki(self) -> None:
for prefix in ("page.winws1_user_presets", "page.winws2_user_presets"):
with self.subTest(prefix=prefix):
body = tr(f"{prefix}.info.body", language="ru")
self.assertIn(".txt", body)
self.assertIn("@<config_file>", body)
self.assertIn("%AppData%\\ZapretTwoDev\\preset-zapret2.txt", body)
self.assertIn("Пресетами можно обмениваться напрямую.", body)
self.assertNotIn("https://wiki.zapret.moe/Zapret2/preset", body)
self.assertIn("прямой запуск", body)
def _assert_accessibility(self, widgets) -> None:
expected = {
widgets.get_configs_btn: ("Открыть Forgejo Issues с конфигами", "Обменивайтесь пресетами"),
widgets.create_btn: ("Создать новый пресет", "Создать новый пресет"),
widgets.import_btn: ("Импортировать пресет из файла", "Импорт пресета из файла"),
widgets.open_folder_btn: ("Открыть папку пресетов", "Открыть папку, где лежат ваши пресеты"),
widgets.reset_all_btn: ("Вернуть встроенные пресеты", "изменения во встроенных пресетах будут потеряны"),
widgets.info_btn: ("Показать справку о пресетах", "Что это такое"),
widgets.preset_search_input: ("Поиск пресетов", "Поиск пресетов по имени"),
widgets.presets_list: (
"Список пользовательских пресетов: список пока загружается",
"Стрелки вверх и вниз выбирают пресет, Enter или Пробел активирует пресет или сворачивает и разворачивает папку, Ctrl со стрелкой вверх или вниз перемещает пресет, пресет также можно перетащить мышью, клавиша меню открывает действия",
),
}
self.assertTrue(widgets.presets_info_btn.isHidden())
for widget, (name, description) in expected.items():
with self.subTest(name=name):
self.assertEqual(widget.accessibleName(), name)
self.assertIn(description, widget.accessibleDescription())
self.assertEqual(widget.property("screenReaderStateText"), name)
search_description = widgets.preset_search_input.accessibleDescription()
self.assertIn("Ctrl+F переводит курсор в поле поиска", search_description)
self.assertIn("После ввода перейдите в список клавишей Tab", search_description)
self.assertIn("или нажмите Стрелка вниз", search_description)
self.assertIn("выберите пресет стрелками вверх и вниз", search_description)
self.assertIn("нажмите Enter или Пробел", search_description)
search_buttons = [
child
for child in widgets.preset_search_input.findChildren(object)
if str(getattr(child, "objectName", lambda: "")() or "") == "lineEditButton"
and hasattr(child, "setFocusPolicy")
]
self.assertTrue(search_buttons)
self.assertTrue(all(button.focusPolicy() == Qt.FocusPolicy.NoFocus for button in search_buttons))
self.assertEqual(
widgets.presets_list.property("screenReaderStateText"),
"Список пользовательских пресетов: список пока загружается",
)
if __name__ == "__main__":
unittest.main()