fix(ui): пункты меню не прячутся под футером; сквозные сценарии панели
Some checks failed
ci / test (3.10) (push) Has been cancelled
ci / test (3.11) (push) Has been cancelled
ci / test (3.12) (push) Has been cancelled

web/index.html:
- мобильное меню после раскрытия не держит анимационную высоту Bootstrap:
  нижние пункты (Обзор/История/API-ключи) уезжали под .sidebar-footer и не нажимались
- на десктопе рельс #nav скроллится сам, футер не сжимается; активный пункт
  доскролливается в область видимости
- failure() принимает заголовок: ошибка валидации источника больше не подписана
  «Не удалось загрузить данные»

tools/reachability.py: общая проверка «элемент виден, но перекрыт другим» —
hit-test по центру с scrollIntoView, пропуск скрытых узлов и закрытых <details>.

tools/panel_scenarios.py: 11 сквозных сценариев реального пользователя —
deep-link фильтр, экспорт CSV/JSON по байтам, читалка и ссылка на оригинал,
дайджест, поиск источников, Telegram-сессия, форма источника (отказ без записи
в реестр), клавиатурный flow (/ ? Esc 1-9) со сверкой строк ленты с API,
мобильное меню, адаптивность 320-1920 без горизонтального переполнения, зум.

tools/ui_smoke.py: reachability-проверки десктопного рельса, контролов и мобильного меню.
tests: +3 регрессии на CSS/заголовки ошибок; pytest 93 passed, smoke exit 0,
сценарии exit 0, page_errors пуст.
This commit is contained in:
Vlad Gothbreach
2026-10-27 05:45:14 +03:00
parent 0831984918
commit 92bf9eaa12
6 changed files with 699 additions and 6 deletions

View File

@@ -121,3 +121,40 @@ def test_panel_typography_does_not_clip_cyrillic():
assert rule, selector
line_height = re.search(r"line-height: ([\d.]+)", rule.group(0))
assert line_height and float(line_height.group(1)) >= 1.4, rule.group(0)
def test_sidebar_menu_never_clamps_under_its_footer():
"""Mobile kept Bootstrap's animated height, so the lower nav links slid under .sidebar-footer:
they had boxes in DevTools but no tap reached them. Desktop rail scrolls instead of overlapping."""
html = (WEB_ROOT / "index.html").read_text(encoding="utf-8")
mobile = re.search(r"@media \(max-width: 991\.98px\) \{(.*?)\n\}", html, re.S)
assert mobile, "mobile media query missing"
assert "#sidebar-menu.show:not(.collapsing) { height: auto !important" in mobile.group(1)
assert "#sidebar-menu > #nav { flex: 0 0 auto; overflow: visible; }" in mobile.group(1)
assert ".sidebar-footer { flex: 0 0 auto;" in mobile.group(1)
desktop = re.search(r"@media \(min-width: 992px\) \{(.*?)\n\}", html, re.S)
assert desktop, "desktop media query missing"
assert "#sidebar-menu > #nav { flex: 1 1 auto; min-height: 0; overflow-y: auto" in desktop.group(1)
assert re.search(r"(?m)^\.sidebar-footer \{ flex: 0 0 auto;", html), "footer must not shrink"
def test_form_errors_are_labelled_for_their_situation():
"""A rejected source used to render «Не удалось загрузить данные» - a load failure title
for a plain validation mistake. failure() now takes a title, and the form passes its own."""
html = (WEB_ROOT / "index.html").read_text(encoding="utf-8")
assert 'function failure(error,retry,title="Не удалось загрузить данные")' in html
assert '<h3 class="alert-title">${esc(title)}</h3>' in html
call = (
'failure(new Error("Нужен адрес с протоколом https:// или http://")'
',null,"Проверьте адрес источника")'
)
assert call in html
def test_active_nav_scrolls_into_view_in_the_desktop_rail():
"""The desktop rail scrolls, so a deep-linked section below the fold must reveal itself."""
html = (WEB_ROOT / "index.html").read_text(encoding="utf-8")
assert (
'if(rail.scrollHeight>rail.clientHeight)'
'rail.querySelector(".nav-link.active")?.scrollIntoView({block:"nearest"})'
) in html