fix(ui): пункты меню не прячутся под футером; сквозные сценарии панели
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user