513 lines
21 KiB
Python
513 lines
21 KiB
Python
import os
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
|
|
|
from PyQt6.QtCore import Qt
|
|
from PyQt6.QtWidgets import QApplication, QLabel, QTableWidget, QWidget
|
|
from qfluentwidgets import PushButton
|
|
|
|
from blockcheck.page_run_workflow import request_blockcheck_stop, reset_blockcheck_running_ui, start_blockcheck_page_run
|
|
from blockcheck.ui.page import BlockcheckPage
|
|
|
|
|
|
class _FeatureStub:
|
|
pass
|
|
|
|
|
|
class BlockcheckPageAccessibilityTests(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls) -> None:
|
|
cls.app = QApplication.instance() or QApplication([])
|
|
|
|
def test_main_tab_controls_are_named_for_screen_reader(self) -> None:
|
|
with patch.object(BlockcheckPage, "_request_page_initial_state_load", lambda self: None):
|
|
page = BlockcheckPage(
|
|
blockcheck_feature=_FeatureStub(),
|
|
diagnostics_feature=_FeatureStub(),
|
|
dns_feature=_FeatureStub(),
|
|
create_strategy_scan_worker=lambda *_args, **_kwargs: None,
|
|
)
|
|
self.addCleanup(page.deleteLater)
|
|
|
|
self.assertEqual(page._tabs_pivot.accessibleName(), "Раздел BlockCheck, выбрано: BlockCheck")
|
|
self.assertIn("BlockCheck, Подбор стратегии, Диагностика или DNS подмена", page._tabs_pivot.accessibleDescription())
|
|
self.assertEqual(
|
|
page._tabs_pivot.items[page.TAB_BLOCKCHECK].accessibleName(),
|
|
"Раздел BlockCheck: BlockCheck, выбрано",
|
|
)
|
|
self.assertEqual(
|
|
page._tabs_pivot.items[page.TAB_STRATEGY_SCAN].accessibleName(),
|
|
"Раздел BlockCheck: Подбор стратегии, не выбрано",
|
|
)
|
|
page._tabs_pivot.setCurrentItem(page.TAB_STRATEGY_SCAN)
|
|
self.assertEqual(page._tabs_pivot.accessibleName(), "Раздел BlockCheck, выбрано: Подбор стратегии")
|
|
self.assertEqual(
|
|
page._tabs_pivot.property("screenReaderStateText"),
|
|
"Раздел BlockCheck, выбрано: Подбор стратегии",
|
|
)
|
|
self.assertEqual(
|
|
page._tabs_pivot.items[page.TAB_BLOCKCHECK].accessibleName(),
|
|
"Раздел BlockCheck: BlockCheck, не выбрано",
|
|
)
|
|
self.assertEqual(
|
|
page._tabs_pivot.items[page.TAB_STRATEGY_SCAN].accessibleName(),
|
|
"Раздел BlockCheck: Подбор стратегии, выбрано",
|
|
)
|
|
|
|
self.assertEqual(page._mode_combo.accessibleName(), "Режим BlockCheck, выбрано: Полная")
|
|
self.assertEqual(
|
|
page._mode_combo.property("screenReaderStateText"),
|
|
"Режим BlockCheck, выбрано: Полная",
|
|
)
|
|
self.assertIn("Выберите глубину проверки", page._mode_combo.accessibleDescription())
|
|
self.assertEqual(page._skip_failed_cb.accessibleName(), "Пропускать проблемные домены, выключено")
|
|
self.assertEqual(
|
|
page._skip_failed_cb.property("screenReaderStateText"),
|
|
"Пропускать проблемные домены, выключено",
|
|
)
|
|
self.assertIn("DNS-заглушкой", page._skip_failed_cb.accessibleDescription())
|
|
self.assertEqual(page._start_btn.accessibleName(), "Запустить BlockCheck")
|
|
self.assertIn("анализ блокировок", page._start_btn.accessibleDescription())
|
|
self.assertEqual(page._start_btn.property("screenReaderStateText"), "Запустить BlockCheck")
|
|
self.assertEqual(page._stop_btn.accessibleName(), "Остановить BlockCheck")
|
|
self.assertIn("Остановить текущую проверку", page._stop_btn.accessibleDescription())
|
|
self.assertEqual(page._stop_btn.property("screenReaderStateText"), "Остановить BlockCheck")
|
|
self.assertEqual(page._domain_input.accessibleName(), "Пользовательский домен для BlockCheck")
|
|
self.assertEqual(
|
|
page._domain_input.property("screenReaderStateText"),
|
|
"Пользовательский домен для BlockCheck",
|
|
)
|
|
self.assertEqual(page._add_domain_btn.accessibleName(), "Добавить домен в BlockCheck")
|
|
self.assertEqual(
|
|
page._add_domain_btn.property("screenReaderStateText"),
|
|
"Добавить домен в BlockCheck",
|
|
)
|
|
self.assertEqual(page._progress_bar.accessibleName(), "Ход BlockCheck: не выполняется")
|
|
self.assertEqual(
|
|
page._progress_bar.property("screenReaderStateText"),
|
|
"Ход BlockCheck: не выполняется",
|
|
)
|
|
self.assertIn("Показывает", page._progress_bar.accessibleDescription())
|
|
self.assertEqual(page._status_label.accessibleName(), "Статус BlockCheck: Готово")
|
|
page._ensure_run_output_ui()
|
|
self.assertEqual(page._log_edit.accessibleName(), "Подробный лог BlockCheck: пока нет записей")
|
|
self.assertEqual(
|
|
page._log_edit.property("screenReaderStateText"),
|
|
"Подробный лог BlockCheck: пока нет записей",
|
|
)
|
|
self.assertEqual(page._expand_log_btn.accessibleName(), "Развернуть лог BlockCheck")
|
|
self.assertEqual(page._prepare_support_btn.accessibleName(), "Подготовить обращение по BlockCheck")
|
|
self.assertEqual(
|
|
page._support_status_label.property("screenReaderStateText"),
|
|
"Статус обращения BlockCheck: нет статуса",
|
|
)
|
|
|
|
def test_blockcheck_section_labels_have_screen_reader_state(self) -> None:
|
|
from blockcheck.ui.sections_build import build_actions_section, build_results_section
|
|
|
|
actions = build_actions_section(
|
|
tr_fn=lambda _key, default: default,
|
|
strong_body_label_cls=QLabel,
|
|
quick_actions_bar_cls=_ActionBarStub,
|
|
content_parent=QWidget(),
|
|
push_button_cls=PushButton,
|
|
qta_module=None,
|
|
on_start=lambda: None,
|
|
on_stop=lambda: None,
|
|
)
|
|
results = build_results_section(
|
|
tr_fn=lambda _key, default: default,
|
|
settings_card_cls=_SettingsCardStub,
|
|
strong_body_label_cls=QLabel,
|
|
table_widget_cls=QTableWidget,
|
|
)
|
|
|
|
self.assertEqual(
|
|
actions.title_label.property("screenReaderStateText"),
|
|
"Раздел BlockCheck: Действия",
|
|
)
|
|
self.assertEqual(
|
|
results.domains_section_label.property("screenReaderStateText"),
|
|
"Раздел результатов BlockCheck: Часть 1: Проверка доменов (TLS + HTTP injection)",
|
|
)
|
|
self.assertEqual(
|
|
results.tcp_section_label.property("screenReaderStateText"),
|
|
"Раздел результатов BlockCheck: Часть 2: Проверка TCP 16-20KB",
|
|
)
|
|
|
|
def test_first_open_does_not_build_run_output_until_needed(self) -> None:
|
|
with patch.object(BlockcheckPage, "_request_page_initial_state_load", lambda self: None):
|
|
page = BlockcheckPage(
|
|
blockcheck_feature=_FeatureStub(),
|
|
diagnostics_feature=_FeatureStub(),
|
|
dns_feature=_FeatureStub(),
|
|
create_strategy_scan_worker=lambda *_args, **_kwargs: None,
|
|
)
|
|
self.addCleanup(page.deleteLater)
|
|
|
|
self.assertIsNone(page._results_card)
|
|
self.assertIsNone(page._dpi_card)
|
|
self.assertIsNone(page._log_card)
|
|
self.assertIsNone(page._table)
|
|
self.assertIsNone(page._log_edit)
|
|
|
|
page._ensure_run_output_ui()
|
|
|
|
self.assertIsNotNone(page._results_card)
|
|
self.assertIsNotNone(page._dpi_card)
|
|
self.assertIsNotNone(page._log_card)
|
|
self.assertIsNotNone(page._table)
|
|
self.assertIsNotNone(page._log_edit)
|
|
|
|
def test_mode_combo_menu_items_are_named_for_screen_reader(self) -> None:
|
|
with patch.object(BlockcheckPage, "_request_page_initial_state_load", lambda self: None):
|
|
page = BlockcheckPage(
|
|
blockcheck_feature=_FeatureStub(),
|
|
diagnostics_feature=_FeatureStub(),
|
|
dns_feature=_FeatureStub(),
|
|
create_strategy_scan_worker=lambda *_args, **_kwargs: None,
|
|
)
|
|
self.addCleanup(page.deleteLater)
|
|
create_menu = getattr(page._mode_combo, "_create_accessible_combo_menu", None)
|
|
self.assertIsNotNone(create_menu)
|
|
|
|
menu = create_menu()
|
|
|
|
self.assertEqual(
|
|
menu.view.item(0).data(Qt.ItemDataRole.AccessibleTextRole),
|
|
"Режим BlockCheck: Быстрая, не выбран",
|
|
)
|
|
self.assertEqual(
|
|
menu.view.item(1).data(Qt.ItemDataRole.AccessibleTextRole),
|
|
"Режим BlockCheck: Полная, выбран",
|
|
)
|
|
|
|
def test_run_progress_bar_reads_running_state_for_screen_reader(self) -> None:
|
|
progress_bar = _FakeProgressBar()
|
|
start_button = _ButtonStub("Запустить BlockCheck")
|
|
stop_button = _ButtonStub("Остановить BlockCheck")
|
|
mode_combo = _ButtonStub("Режим BlockCheck")
|
|
skip_failed_checkbox = _ButtonStub("Пропускать проблемные домены")
|
|
status_label = _TextStub()
|
|
|
|
start_blockcheck_page_run(
|
|
blockcheck_feature=_RunFeatureStub(),
|
|
mode="full",
|
|
extra_domains=[],
|
|
skip_preflight_failed=False,
|
|
parent=None,
|
|
run_runtime=_RunRuntimeStub(),
|
|
table=_TableStub(),
|
|
tcp_table=None,
|
|
tcp_section_label=None,
|
|
dpi_card=_WidgetStub(),
|
|
log_edit=_LogEditStub(),
|
|
start_button=start_button,
|
|
stop_button=stop_button,
|
|
mode_combo=mode_combo,
|
|
skip_failed_checkbox=skip_failed_checkbox,
|
|
progress_bar=progress_bar,
|
|
status_label=status_label,
|
|
runtime_warnings_seen=set(),
|
|
set_support_status=lambda _text: None,
|
|
tr_fn=lambda _key, default: default,
|
|
on_phase_changed=lambda *_args: None,
|
|
on_test_result=lambda *_args: None,
|
|
on_target_complete=lambda *_args: None,
|
|
on_log=lambda *_args: None,
|
|
on_run_log_started=lambda *_args: None,
|
|
on_finished=lambda *_args: None,
|
|
)
|
|
|
|
self.assertEqual(progress_bar.accessibleName(), "Ход BlockCheck: выполняется")
|
|
self.assertEqual(progress_bar.property("screenReaderStateText"), "Ход BlockCheck: выполняется")
|
|
self.assertEqual(status_label.accessibleName(), "Статус BlockCheck: Запуск тестов...")
|
|
self.assertEqual(status_label.property("screenReaderStateText"), "Статус BlockCheck: Запуск тестов...")
|
|
self.assertEqual(start_button.accessibleName(), "Запустить BlockCheck, недоступно")
|
|
self.assertEqual(stop_button.accessibleName(), "Остановить BlockCheck, доступно")
|
|
self.assertEqual(mode_combo.accessibleName(), "Режим BlockCheck, недоступно во время проверки")
|
|
self.assertEqual(
|
|
skip_failed_checkbox.accessibleName(),
|
|
"Пропускать проблемные домены, недоступно во время проверки",
|
|
)
|
|
|
|
reset_blockcheck_running_ui(
|
|
start_button=start_button,
|
|
stop_button=stop_button,
|
|
mode_combo=mode_combo,
|
|
skip_failed_checkbox=skip_failed_checkbox,
|
|
progress_bar=progress_bar,
|
|
)
|
|
|
|
self.assertEqual(progress_bar.accessibleName(), "Ход BlockCheck: не выполняется")
|
|
self.assertEqual(progress_bar.property("screenReaderStateText"), "Ход BlockCheck: не выполняется")
|
|
self.assertEqual(start_button.accessibleName(), "Запустить BlockCheck, доступно")
|
|
self.assertEqual(stop_button.accessibleName(), "Остановить BlockCheck, недоступно")
|
|
self.assertEqual(mode_combo.accessibleName(), "Режим BlockCheck, доступно")
|
|
self.assertEqual(skip_failed_checkbox.accessibleName(), "Пропускать проблемные домены, доступно")
|
|
|
|
def test_stop_request_reads_stopping_state_for_screen_reader(self) -> None:
|
|
worker = _WorkerStub()
|
|
stop_button = _ButtonStub("Остановить BlockCheck")
|
|
status_label = _TextStub()
|
|
|
|
request_blockcheck_stop(
|
|
worker=worker,
|
|
stop_button=stop_button,
|
|
status_label=status_label,
|
|
force_stop=lambda _worker: None,
|
|
tr_fn=lambda _key, default: default,
|
|
)
|
|
|
|
self.assertTrue(worker.stopped)
|
|
self.assertEqual(stop_button.accessibleName(), "Остановить BlockCheck, недоступно")
|
|
self.assertEqual(status_label.accessibleName(), "Статус BlockCheck: Остановка...")
|
|
self.assertEqual(status_label.property("screenReaderStateText"), "Статус BlockCheck: Остановка...")
|
|
|
|
def test_run_start_restores_empty_result_table_screen_reader_states(self) -> None:
|
|
results_table = _AccessibleTableStub()
|
|
tcp_table = _AccessibleTableStub()
|
|
|
|
start_blockcheck_page_run(
|
|
blockcheck_feature=_RunFeatureStub(),
|
|
mode="full",
|
|
extra_domains=[],
|
|
skip_preflight_failed=False,
|
|
parent=None,
|
|
run_runtime=_RunRuntimeStub(),
|
|
table=results_table,
|
|
tcp_table=tcp_table,
|
|
tcp_section_label=_WidgetStub(),
|
|
dpi_card=_WidgetStub(),
|
|
log_edit=_LogEditStub(),
|
|
start_button=_ButtonStub(),
|
|
stop_button=_ButtonStub(),
|
|
mode_combo=_ButtonStub(),
|
|
skip_failed_checkbox=_ButtonStub(),
|
|
progress_bar=_FakeProgressBar(),
|
|
status_label=_TextStub(),
|
|
runtime_warnings_seen=set(),
|
|
set_support_status=lambda _text: None,
|
|
tr_fn=lambda _key, default: default,
|
|
on_phase_changed=lambda *_args: None,
|
|
on_test_result=lambda *_args: None,
|
|
on_target_complete=lambda *_args: None,
|
|
on_log=lambda *_args: None,
|
|
on_run_log_started=lambda *_args: None,
|
|
on_finished=lambda *_args: None,
|
|
)
|
|
|
|
self.assertEqual(results_table.row_count, 0)
|
|
self.assertEqual(results_table.accessibleName(), "Результаты BlockCheck по доменам: пока нет результатов")
|
|
self.assertEqual(
|
|
results_table.property("screenReaderStateText"),
|
|
"Результаты BlockCheck по доменам: пока нет результатов",
|
|
)
|
|
self.assertEqual(tcp_table.row_count, 0)
|
|
self.assertFalse(tcp_table.visible)
|
|
self.assertEqual(tcp_table.accessibleName(), "Результаты TCP 16-20KB: пока нет результатов")
|
|
self.assertEqual(
|
|
tcp_table.property("screenReaderStateText"),
|
|
"Результаты TCP 16-20KB: пока нет результатов",
|
|
)
|
|
|
|
|
|
class _SignalStub:
|
|
def connect(self, _callback) -> None:
|
|
pass
|
|
|
|
|
|
class _ActionBarStub:
|
|
def __init__(self, *_args, **_kwargs) -> None:
|
|
self.buttons = []
|
|
|
|
def add_button(self, button) -> None:
|
|
self.buttons.append(button)
|
|
|
|
|
|
class _SettingsCardStub:
|
|
def __init__(self, title: str) -> None:
|
|
self.title = str(title)
|
|
self.widgets = []
|
|
|
|
def add_widget(self, widget) -> None:
|
|
self.widgets.append(widget)
|
|
|
|
|
|
class _WorkerStub:
|
|
def __init__(self) -> None:
|
|
self.phase_changed = _SignalStub()
|
|
self.test_result = _SignalStub()
|
|
self.target_complete = _SignalStub()
|
|
self.log_message = _SignalStub()
|
|
self.run_log_started = _SignalStub()
|
|
self.finished = _SignalStub()
|
|
self.stopped = False
|
|
|
|
def stop(self) -> None:
|
|
self.stopped = True
|
|
|
|
|
|
class _RunFeatureStub:
|
|
def create_blockcheck_worker(self, **_kwargs):
|
|
return _WorkerStub()
|
|
|
|
|
|
class _RunRuntimeStub:
|
|
def start_qobject_worker(self, *, parent, worker_factory) -> None:
|
|
worker_factory(1)
|
|
|
|
|
|
class _TableStub:
|
|
def setRowCount(self, _count: int) -> None: # noqa: N802
|
|
pass
|
|
|
|
|
|
class _AccessibleTableStub:
|
|
def __init__(self) -> None:
|
|
self.row_count = -1
|
|
self.visible = True
|
|
self.properties = {}
|
|
self.accessible_name = ""
|
|
self.accessible_description = ""
|
|
|
|
def setRowCount(self, count: int) -> None: # noqa: N802
|
|
self.row_count = int(count)
|
|
|
|
def setVisible(self, visible: bool) -> None: # noqa: N802
|
|
self.visible = bool(visible)
|
|
|
|
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) -> object:
|
|
return self.properties.get(name)
|
|
|
|
def setProperty(self, name: str, value: object) -> None: # noqa: N802
|
|
self.properties[name] = value
|
|
|
|
|
|
class _WidgetStub:
|
|
def setVisible(self, _visible: bool) -> None: # noqa: N802
|
|
pass
|
|
|
|
|
|
class _LogEditStub:
|
|
def clear(self) -> None:
|
|
pass
|
|
|
|
|
|
class _ButtonStub:
|
|
def __init__(self, text: str = "") -> None:
|
|
self._enabled = True
|
|
self._text = str(text)
|
|
self.properties = {}
|
|
self.accessible_name = ""
|
|
self.accessible_description = ""
|
|
|
|
def text(self) -> str:
|
|
return self._text
|
|
|
|
def isEnabled(self) -> bool: # noqa: N802
|
|
return self._enabled
|
|
|
|
def setEnabled(self, enabled: bool) -> None: # noqa: N802
|
|
self._enabled = bool(enabled)
|
|
|
|
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) -> object:
|
|
return self.properties.get(name)
|
|
|
|
def setProperty(self, name: str, value: object) -> None: # noqa: N802
|
|
self.properties[name] = value
|
|
|
|
|
|
class _TextStub:
|
|
def __init__(self) -> None:
|
|
self.properties = {}
|
|
self.accessible_name = ""
|
|
self.accessible_description = ""
|
|
|
|
def setText(self, text: str) -> None: # noqa: N802
|
|
self.text = text
|
|
|
|
def text(self) -> str:
|
|
return getattr(self, "text", "")
|
|
|
|
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) -> object:
|
|
return self.properties.get(name)
|
|
|
|
def setProperty(self, name: str, value: object) -> None: # noqa: N802
|
|
self.properties[name] = value
|
|
|
|
|
|
class _FakeProgressBar:
|
|
def __init__(self) -> None:
|
|
self.properties = {}
|
|
self.accessible_name = ""
|
|
self.accessible_description = ""
|
|
|
|
def setVisible(self, _visible: bool) -> None: # noqa: N802
|
|
pass
|
|
|
|
def start(self) -> None:
|
|
pass
|
|
|
|
def stop(self) -> None:
|
|
pass
|
|
|
|
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) -> object:
|
|
return self.properties.get(name)
|
|
|
|
def setProperty(self, name: str, value: object) -> None: # noqa: N802
|
|
self.properties[name] = value
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|