All checks were successful
Zapret source guards / validate (push) Successful in 4s
Obsidian Publish (publish.obsidian.md/zapret) закрыт, вики переехала на self-hosted Quartz: https://wiki.zapret.moe. Пути заметок сохранены, префикс Privacy/ из старых URL убран. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
770 lines
34 KiB
Python
770 lines
34 KiB
Python
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()
|