465 lines
22 KiB
Python
465 lines
22 KiB
Python
from __future__ import annotations
|
||
|
||
import os
|
||
import unittest
|
||
from datetime import datetime
|
||
from types import SimpleNamespace
|
||
from unittest.mock import Mock, patch
|
||
|
||
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||
|
||
from qfluentwidgets import PushButton, SubtitleLabel
|
||
from PyQt6.QtWidgets import QApplication, QWidget
|
||
|
||
from donater.ui.build import (
|
||
build_premium_actions_section,
|
||
build_premium_activation_section,
|
||
build_premium_device_info_section,
|
||
)
|
||
from donater.ui.page_plans import build_connection_test_start_plan
|
||
from donater.ui.pairing_workflow import (
|
||
apply_device_info_snapshot_labels,
|
||
apply_pair_code_result_ui,
|
||
apply_pair_code_start_ui,
|
||
)
|
||
from donater.ui.page_lifecycle import apply_premium_language, render_activation_status_label
|
||
from donater.ui.status_workflow import (
|
||
apply_connection_test_plan,
|
||
apply_status_check_success,
|
||
render_server_status_label,
|
||
)
|
||
from donater.ui.accessibility import apply_premium_button_accessibility
|
||
from donater.ui.page import PremiumPage
|
||
from donater.ui.status_card import StatusCard
|
||
|
||
|
||
class _DialogButton:
|
||
def __init__(self) -> None:
|
||
self._accessible_name = ""
|
||
self._accessible_description = ""
|
||
|
||
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)
|
||
|
||
|
||
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.exec_called = False
|
||
_MessageBox.instances.append(self)
|
||
|
||
def exec(self) -> bool:
|
||
self.exec_called = True
|
||
return False
|
||
|
||
|
||
class PremiumControlsAccessibilityTests(unittest.TestCase):
|
||
@classmethod
|
||
def setUpClass(cls) -> None:
|
||
cls._app = QApplication.instance() or QApplication([])
|
||
|
||
def test_premium_buttons_have_screen_reader_names(self) -> None:
|
||
parent = QWidget()
|
||
|
||
activation = build_premium_activation_section(
|
||
tr=lambda _key, default: default,
|
||
on_create_pair_code=lambda: None,
|
||
)
|
||
device = build_premium_device_info_section(
|
||
tr=lambda _key, default: default,
|
||
on_open_bot=lambda: None,
|
||
)
|
||
actions = build_premium_actions_section(
|
||
parent=parent,
|
||
tr=lambda _key, default: default,
|
||
on_check_status=lambda: None,
|
||
on_change_key=lambda: None,
|
||
on_test_connection=lambda: None,
|
||
on_open_bot=lambda: None,
|
||
)
|
||
|
||
self.assertEqual(activation.activate_btn.accessibleName(), "Создать код привязки Premium")
|
||
self.assertEqual(activation.activate_btn.property("screenReaderStateText"), "Создать код привязки Premium")
|
||
self.assertIn("Telegram", activation.activate_btn.accessibleDescription())
|
||
self.assertEqual(
|
||
activation.instructions_label.property("screenReaderStateText"),
|
||
(
|
||
"Инструкция Premium: 1. Нажмите «Создать код» "
|
||
"2. Отправьте код боту @zapretvpns_bot в Telegram (сообщением) "
|
||
"3. Вернитесь сюда — приложение обновит статус автоматически"
|
||
),
|
||
)
|
||
self.assertIn("как привязать Premium", activation.instructions_label.accessibleDescription())
|
||
self.assertEqual(device.open_bot_btn.accessibleName(), "Открыть Premium-бота")
|
||
self.assertIn("Telegram", device.open_bot_btn.accessibleDescription())
|
||
self.assertEqual(actions.refresh_btn.accessibleName(), "Обновить Premium-статус")
|
||
self.assertIn("Premium-статус", actions.refresh_btn.accessibleDescription())
|
||
self.assertEqual(actions.change_key_btn.accessibleName(), "Сбросить Premium-активацию")
|
||
self.assertIn("Удаляет токен", actions.change_key_btn.accessibleDescription())
|
||
self.assertEqual(actions.test_btn.accessibleName(), "Проверить соединение Premium")
|
||
self.assertEqual(actions.test_btn.property("screenReaderStateText"), "Проверить соединение Premium")
|
||
self.assertIn("Premium backend", actions.test_btn.accessibleDescription())
|
||
self.assertEqual(actions.extend_btn.accessibleName(), "Продлить Premium-подписку")
|
||
self.assertIn("Telegram-бота", actions.extend_btn.accessibleDescription())
|
||
self.assertEqual(activation.key_input.accessibleName(), "Код привязки Premium: пока не создан")
|
||
self.assertIn("код, который нужно отправить", activation.key_input.accessibleDescription())
|
||
|
||
def test_disabled_premium_buttons_report_unavailable_state(self) -> None:
|
||
activate_btn = PushButton("Создать код")
|
||
test_btn = PushButton("Проверить соединение")
|
||
self.addCleanup(activate_btn.deleteLater)
|
||
self.addCleanup(test_btn.deleteLater)
|
||
activate_btn.setEnabled(False)
|
||
test_btn.setEnabled(False)
|
||
|
||
apply_premium_button_accessibility(
|
||
tr_fn=lambda _key, default: default,
|
||
activate_btn=activate_btn,
|
||
test_btn=test_btn,
|
||
)
|
||
|
||
self.assertEqual(activate_btn.accessibleName(), "Создать код привязки Premium, недоступно")
|
||
self.assertEqual(
|
||
activate_btn.property("screenReaderStateText"),
|
||
"Создать код привязки Premium, недоступно",
|
||
)
|
||
self.assertIn("сейчас недоступно", activate_btn.accessibleDescription().lower())
|
||
self.assertEqual(test_btn.accessibleName(), "Проверить соединение Premium, недоступно")
|
||
self.assertEqual(
|
||
test_btn.property("screenReaderStateText"),
|
||
"Проверить соединение Premium, недоступно",
|
||
)
|
||
self.assertIn("сейчас недоступно", test_btn.accessibleDescription().lower())
|
||
|
||
def test_reset_activation_dialog_buttons_are_named_for_screen_reader(self) -> None:
|
||
page = PremiumPage.__new__(PremiumPage)
|
||
page._request_reset_storage = Mock()
|
||
page._tr = lambda _key, default, **kwargs: default.format(**kwargs) if kwargs else default
|
||
page.window = lambda: None
|
||
_MessageBox.instances = []
|
||
|
||
with patch("donater.ui.page.MessageBox", _MessageBox):
|
||
PremiumPage._change_key(page)
|
||
|
||
dialog = _MessageBox.instances[0]
|
||
self.assertEqual(dialog.yesButton.accessibleName(), "Сбросить Premium-активацию")
|
||
self.assertIn(
|
||
"сервер отзовёт точную привязку",
|
||
dialog.yesButton.accessibleDescription(),
|
||
)
|
||
self.assertEqual(dialog.cancelButton.accessibleName(), "Отменить сброс Premium-активации")
|
||
self.assertTrue(dialog.exec_called)
|
||
page._request_reset_storage.assert_not_called()
|
||
|
||
def test_premium_device_info_labels_expose_state_text(self) -> None:
|
||
device = build_premium_device_info_section(
|
||
tr=lambda _key, default: default,
|
||
on_open_bot=lambda: None,
|
||
)
|
||
|
||
self.assertEqual(device.device_id_label.accessibleName(), "ID устройства: загрузка...")
|
||
self.assertEqual(device.saved_key_label.accessibleName(), "Токен устройства: не найден")
|
||
self.assertEqual(device.last_check_label.accessibleName(), "Последняя проверка Premium: —")
|
||
self.assertEqual(device.server_status_label.accessibleName(), "Статус Premium-сервера: проверка...")
|
||
|
||
apply_device_info_snapshot_labels(
|
||
snapshot={
|
||
"device_id": "abcdef0123456789abcdef",
|
||
"device_token": "token",
|
||
"pair_code": "ABCD12EF",
|
||
"last_check": datetime(2026, 6, 10, 12, 30),
|
||
},
|
||
tr=lambda _key, default, **kwargs: default.format(**kwargs) if kwargs else default,
|
||
device_id_label=device.device_id_label,
|
||
saved_key_label=device.saved_key_label,
|
||
last_check_label=device.last_check_label,
|
||
)
|
||
|
||
self.assertEqual(device.device_id_label.accessibleName(), "ID устройства: abcdef0123456789...")
|
||
self.assertEqual(device.saved_key_label.accessibleName(), "Токен устройства: есть. Код привязки: ABCD12EF")
|
||
self.assertEqual(device.last_check_label.accessibleName(), "Последняя проверка Premium: 10.06.2026 12:30")
|
||
|
||
def test_premium_language_refresh_keeps_screen_reader_names(self) -> None:
|
||
parent = QWidget()
|
||
activation = build_premium_activation_section(
|
||
tr=lambda _key, default: default,
|
||
on_create_pair_code=lambda: None,
|
||
)
|
||
device = build_premium_device_info_section(
|
||
tr=lambda _key, default: default,
|
||
on_open_bot=lambda: None,
|
||
)
|
||
actions = build_premium_actions_section(
|
||
parent=parent,
|
||
tr=lambda _key, default: default,
|
||
on_check_status=lambda: None,
|
||
on_change_key=lambda: None,
|
||
on_test_connection=lambda: None,
|
||
on_open_bot=lambda: None,
|
||
)
|
||
|
||
apply_premium_language(
|
||
tr_fn=lambda _key, default: default,
|
||
activation_in_progress=True,
|
||
connection_test_in_progress=True,
|
||
instructions_label=activation.instructions_label,
|
||
key_input=activation.key_input,
|
||
activate_btn=activation.activate_btn,
|
||
open_bot_btn=device.open_bot_btn,
|
||
refresh_btn=actions.refresh_btn,
|
||
change_key_btn=actions.change_key_btn,
|
||
extend_btn=actions.extend_btn,
|
||
test_btn=actions.test_btn,
|
||
render_server_status_fn=lambda: None,
|
||
render_days_label_fn=lambda: None,
|
||
render_status_badge_fn=lambda: None,
|
||
render_activation_status_fn=lambda: None,
|
||
)
|
||
|
||
self.assertEqual(activation.activate_btn.accessibleName(), "Создание кода привязки Premium")
|
||
self.assertEqual(activation.activate_btn.property("screenReaderStateText"), "Создание кода привязки Premium")
|
||
self.assertEqual(actions.test_btn.accessibleName(), "Проверка соединения Premium выполняется")
|
||
self.assertEqual(actions.test_btn.property("screenReaderStateText"), "Проверка соединения Premium выполняется")
|
||
self.assertEqual(actions.refresh_btn.accessibleName(), "Обновить Premium-статус")
|
||
self.assertEqual(actions.extend_btn.accessibleName(), "Продлить Premium-подписку")
|
||
self.assertEqual(activation.key_input.accessibleName(), "Код привязки Premium: пока не создан")
|
||
self.assertEqual(
|
||
activation.instructions_label.property("screenReaderStateText"),
|
||
(
|
||
"Инструкция Premium: 1. Нажмите «Создать код» "
|
||
"2. Отправьте код боту @zapretvpns_bot в Telegram (сообщением) "
|
||
"3. Вернитесь сюда — приложение обновит статус автоматически"
|
||
),
|
||
)
|
||
|
||
def test_linked_status_clears_consumed_pair_code_from_ui(self) -> None:
|
||
activation = build_premium_activation_section(
|
||
tr=lambda _key, default, **kwargs: default.format(**kwargs),
|
||
on_create_pair_code=lambda: None,
|
||
)
|
||
activation.key_input.setText("7YRFE33E")
|
||
activation_status = Mock()
|
||
|
||
apply_status_check_success(
|
||
{
|
||
"activated": True,
|
||
"is_premium": True,
|
||
"found": True,
|
||
"days_remaining": 30,
|
||
"status": "Активировано",
|
||
},
|
||
tr=lambda _key, default, **kwargs: default.format(**kwargs),
|
||
refresh_btn=SimpleNamespace(set_loading=lambda _value: None),
|
||
key_input=activation.key_input,
|
||
update_device_info=lambda: None,
|
||
set_status_badge=lambda **_kwargs: None,
|
||
set_activation_status=activation_status,
|
||
set_activation_section_visible=lambda _visible: None,
|
||
stop_autopoll=lambda: None,
|
||
sync_autopoll=lambda: None,
|
||
apply_subscription_state=lambda _premium, _days: None,
|
||
)
|
||
|
||
self.assertEqual(activation.key_input.text(), "")
|
||
self.assertEqual(
|
||
activation.key_input.accessibleName(),
|
||
"Код привязки Premium: пока не создан",
|
||
)
|
||
activation_status.assert_called_once_with(
|
||
text_key="page.premium.activation.success.linked_active",
|
||
text_default="✅ Устройство привязано. Premium активен.",
|
||
)
|
||
|
||
def test_premium_runtime_actions_update_screen_reader_names(self) -> None:
|
||
parent = QWidget()
|
||
activation = build_premium_activation_section(
|
||
tr=lambda _key, default: default,
|
||
on_create_pair_code=lambda: None,
|
||
)
|
||
actions = build_premium_actions_section(
|
||
parent=parent,
|
||
tr=lambda _key, default: default,
|
||
on_check_status=lambda: None,
|
||
on_change_key=lambda: None,
|
||
on_test_connection=lambda: None,
|
||
on_open_bot=lambda: None,
|
||
)
|
||
|
||
apply_pair_code_start_ui(
|
||
activate_btn=activation.activate_btn,
|
||
key_input=activation.key_input,
|
||
tr=lambda _key, default: default,
|
||
set_activation_status=lambda **_kwargs: None,
|
||
stop_autopoll=lambda: None,
|
||
)
|
||
apply_connection_test_plan(
|
||
build_connection_test_start_plan(checker_ready=True),
|
||
tr=lambda _key, default: default,
|
||
test_btn=actions.test_btn,
|
||
render_server_status=lambda: None,
|
||
set_server_status_state=lambda *_args: None,
|
||
)
|
||
|
||
self.assertEqual(activation.activate_btn.accessibleName(), "Создание кода привязки Premium")
|
||
self.assertEqual(activation.activate_btn.property("screenReaderStateText"), "Создание кода привязки Premium")
|
||
self.assertEqual(actions.test_btn.accessibleName(), "Проверка соединения Premium выполняется")
|
||
self.assertEqual(actions.test_btn.property("screenReaderStateText"), "Проверка соединения Premium выполняется")
|
||
self.assertEqual(activation.key_input.accessibleName(), "Код привязки Premium: пока не создан")
|
||
|
||
apply_pair_code_result_ui(
|
||
(True, "ok", "ABCD12EF"),
|
||
activate_btn=activation.activate_btn,
|
||
key_input=activation.key_input,
|
||
tr=lambda _key, default: default,
|
||
set_activation_status=lambda **_kwargs: None,
|
||
update_device_info=lambda: None,
|
||
start_autopoll=lambda: None,
|
||
stop_autopoll=lambda: None,
|
||
)
|
||
|
||
self.assertEqual(activation.key_input.accessibleName(), "Код привязки Premium: ABCD12EF")
|
||
self.assertEqual(activation.key_input.property("screenReaderStateText"), "Код привязки Premium: ABCD12EF")
|
||
|
||
def test_premium_status_labels_expose_state_text(self) -> None:
|
||
activation = build_premium_activation_section(
|
||
tr=lambda _key, default: default,
|
||
on_create_pair_code=lambda: None,
|
||
)
|
||
device = build_premium_device_info_section(
|
||
tr=lambda _key, default, **kwargs: default.format(**kwargs),
|
||
on_open_bot=lambda: None,
|
||
)
|
||
|
||
render_activation_status_label(
|
||
activation_status_state={
|
||
"text_key": "page.premium.activation.created",
|
||
"text_default": "Код создан: {code}",
|
||
"text_kwargs": {"code": "ABCD12EF"},
|
||
},
|
||
tr_fn=lambda _key, default, **kwargs: default.format(**kwargs),
|
||
activation_status_label=activation.activation_status,
|
||
)
|
||
render_server_status_label(
|
||
device.server_status_label,
|
||
tr=lambda _key, default, **kwargs: default.format(**kwargs),
|
||
mode="result",
|
||
message="соединение работает",
|
||
success=True,
|
||
)
|
||
|
||
self.assertEqual(
|
||
activation.activation_status.property("screenReaderStateText"),
|
||
"Статус Premium-активации: Код создан: ABCD12EF",
|
||
)
|
||
self.assertEqual(
|
||
device.server_status_label.property("screenReaderStateText"),
|
||
"Статус Premium-сервера: соединение работает",
|
||
)
|
||
|
||
def test_premium_page_activation_status_updates_state_text(self) -> None:
|
||
activation = build_premium_activation_section(
|
||
tr=lambda _key, default: default,
|
||
on_create_pair_code=lambda: None,
|
||
)
|
||
page = SimpleNamespace(
|
||
activation_status=activation.activation_status,
|
||
_tr=lambda _key, default, **kwargs: default.format(**kwargs),
|
||
)
|
||
|
||
PremiumPage._set_activation_status(
|
||
page,
|
||
text_key="page.premium.activation.created",
|
||
text_default="Код создан: {code}",
|
||
text_kwargs={"code": "ABCD12EF"},
|
||
)
|
||
|
||
self.assertEqual(
|
||
activation.activation_status.property("screenReaderStateText"),
|
||
"Статус Premium-активации: Код создан: ABCD12EF",
|
||
)
|
||
|
||
def test_premium_days_label_exposes_state_text(self) -> None:
|
||
page = SimpleNamespace(
|
||
days_label=SubtitleLabel(""),
|
||
_tr=lambda _key, default, **kwargs: default.format(**kwargs),
|
||
)
|
||
self.addCleanup(page.days_label.deleteLater)
|
||
|
||
page._days_state_kind = "normal"
|
||
page._days_state_value = 40
|
||
PremiumPage._render_days_label(page)
|
||
|
||
self.assertEqual(page.days_label.accessibleName(), "Осталось дней Premium: 40")
|
||
self.assertEqual(page.days_label.property("screenReaderStateText"), "Осталось дней Premium: 40")
|
||
|
||
page._days_state_kind = "urgent"
|
||
page._days_state_value = 3
|
||
PremiumPage._render_days_label(page)
|
||
|
||
self.assertEqual(page.days_label.accessibleName(), "Premium срочно нужно продлить, осталось дней: 3")
|
||
self.assertEqual(
|
||
page.days_label.property("screenReaderStateText"),
|
||
"Premium срочно нужно продлить, осталось дней: 3",
|
||
)
|
||
|
||
page._days_state_kind = "none"
|
||
page._days_state_value = 0
|
||
PremiumPage._render_days_label(page)
|
||
|
||
self.assertEqual(page.days_label.accessibleName(), "Premium-подписка не активна")
|
||
self.assertEqual(page.days_label.property("screenReaderStateText"), "Premium-подписка не активна")
|
||
|
||
def test_premium_status_card_exposes_state_text(self) -> None:
|
||
card = StatusCard()
|
||
self.addCleanup(card.deleteLater)
|
||
|
||
card.set_status("Premium активен", "Осталось 7 дней", "active")
|
||
|
||
self.assertEqual(card.accessibleName(), "Статус Premium: Premium активен. Осталось 7 дней")
|
||
self.assertEqual(
|
||
card.property("screenReaderStateText"),
|
||
"Статус Premium: Premium активен. Осталось 7 дней",
|
||
)
|
||
|
||
def test_premium_status_card_inner_labels_expose_state_text(self) -> None:
|
||
card = StatusCard()
|
||
self.addCleanup(card.deleteLater)
|
||
|
||
card.set_status("Premium активен", "Осталось 7 дней", "active")
|
||
|
||
self.assertEqual(card._icon_lbl.accessibleName(), "Индикатор Premium: Premium активен. Осталось 7 дней")
|
||
self.assertEqual(card._icon_lbl.property("screenReaderStateText"), "Индикатор Premium: Premium активен. Осталось 7 дней")
|
||
self.assertEqual(card._title_lbl.accessibleName(), "Статус Premium: Premium активен")
|
||
self.assertEqual(card._title_lbl.property("screenReaderStateText"), "Статус Premium: Premium активен")
|
||
self.assertEqual(card._detail_lbl.accessibleName(), "Детали Premium: Осталось 7 дней")
|
||
self.assertEqual(card._detail_lbl.property("screenReaderStateText"), "Детали Premium: Осталось 7 дней")
|
||
|
||
def test_premium_status_card_exposes_initial_empty_state(self) -> None:
|
||
card = StatusCard()
|
||
self.addCleanup(card.deleteLater)
|
||
|
||
self.assertEqual(card.accessibleName(), "Статус Premium: статус пока не загружен")
|
||
self.assertEqual(
|
||
card.property("screenReaderStateText"),
|
||
"Статус Premium: статус пока не загружен",
|
||
)
|
||
self.assertIn("Premium", card.accessibleDescription())
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|