feat(dashboard): replace home with approved minimal design
CI / ruff · pytest (pull_request) Successful in 2m1s

Refs #73
This commit is contained in:
Pavel Eliseev
2026-10-04 14:54:14 +03:00
parent a2b1b2e7a5
commit c01bfc90df
8 changed files with 337 additions and 131 deletions
+78 -2
View File
@@ -23,12 +23,13 @@ class FakeApi:
def __init__(self) -> None:
self.calls: list[tuple[str, str]] = []
self.extra_alerts: list = []
self.patients = list(fixtures.PATIENTS)
def __call__(self, req: httpx.Request) -> httpx.Response:
path = req.url.path.removeprefix("/api/v1")
self.calls.append((req.method, path))
if path == "/doctors/doc-smirnova/patients":
return httpx.Response(200, json=_dump(fixtures.PATIENTS))
return httpx.Response(200, json=_dump(self.patients))
if path.startswith("/patients/tok-"):
card = fixtures.card(path.rsplit("/", 1)[1])
return httpx.Response(200, json=_dump(card)) if card else httpx.Response(404)
@@ -64,6 +65,7 @@ def test_list_page_renders_sorted_patients(api):
assert not at.exception
html = _html(at)
assert "Кабинет д-ра Смирновой Е. А." in html
assert "Пациенты под наблюдением" in html
assert html.index("Андрей С.") < html.index("Сергей П.") < html.index("Павел Р.")
assert "Высокий · 70" in html # discontinuation-risk badge
open_buttons = [b for b in at.button if b.label == "Открыть"]
@@ -72,7 +74,9 @@ def test_list_page_renders_sorted_patients(api):
def test_list_filter_and_open_card(api):
at = AppTest.from_file(LIST_PAGE, default_timeout=20).run()
at.segmented_control(key="status_filter").set_value("🔴 Красные").run()
at.segmented_control(key="status_filter").set_value("Срочно").run()
at.text_input(key="patient_query").set_value("андр").run()
at.toggle(key="patient_details").set_value(True).run()
assert len([b for b in at.button if b.label == "Открыть"]) == 1
at.button(key="open-tok-andrey").click().run()
assert not at.exception
@@ -81,6 +85,76 @@ def test_list_filter_and_open_card(api):
assert "PHQ-9 п.9 = 2" in html and "План безопасности" in html
assert "Решения принимает врач" in html
assert ("GET", "/patients/tok-andrey") in api.calls
next(b for b in at.button if b.label == "Все пациенты").click().run()
assert not at.exception
assert not at.query_params.get("patient")
assert len([b for b in at.button if b.label == "Открыть"]) == 1 # the selected filter is retained
assert at.text_input(key="patient_query").value == "андр"
assert at.toggle(key="patient_details").value is True
@pytest.mark.parametrize("choice,expected", [("Внимание", 4), ("Без уведомлений", 3), ("Все пациенты", 8)])
def test_home_status_filters(api, choice, expected):
at = AppTest.from_file(LIST_PAGE, default_timeout=20).run()
at.segmented_control(key="status_filter").set_value(choice).run()
assert not at.exception
assert len([b for b in at.button if b.label == "Открыть"]) == expected
assert f"Показано {expected} из 8 пациентов" in _html(at)
def test_home_search_empty_result_and_reset(api):
at = AppTest.from_file(LIST_PAGE, default_timeout=20).run()
at.text_input(key="patient_query").set_value(" ПАРОКСЕТИН ").run()
assert len([b for b in at.button if b.label == "Открыть"]) == 1
assert "Сергей П." in _html(at)
at.segmented_control(key="status_filter").set_value("Без уведомлений").run()
assert "Пациенты не найдены" in _html(at)
next(b for b in at.button if b.label == "Сбросить фильтры").click().run()
assert not at.exception
assert len([b for b in at.button if b.label == "Открыть"]) == 8
assert at.text_input(key="patient_query").value == ""
def test_home_empty_cabinet_keeps_connect_action(api):
api.patients = []
at = AppTest.from_file(LIST_PAGE, default_timeout=20).run()
assert not at.exception
assert "Пациентов пока нет" in _html(at)
assert "Показано 0 из 0 пациентов" in _html(at)
assert any(b.label == "Подключить пациента" for b in at.button)
assert not any(b.label == "Открыть" for b in at.button)
def test_home_details_preserve_clinical_information(api):
at = AppTest.from_file(LIST_PAGE, default_timeout=20).run()
assert "PHQ-9" in _html(at) and "PCL-5" in _html(at)
at.toggle(key="patient_details").set_value(True).run()
assert not at.exception
html = _html(at)
assert "сертралин 50 мг" in html
assert "PHQ-9 п.9 = 2 — кризисный протокол" in html
assert "пропуски 3 из 7" in html
def test_home_info_only_patient_and_missing_scores(api):
api.patients = [
fixtures.PATIENTS[0].model_copy(
update={
"status_color": "grey",
"top_alert_level": "info",
"phq9_last": None,
"pcl5_last": None,
"adherence_7d": None,
"open_alerts": 1,
}
)
]
at = AppTest.from_file(LIST_PAGE, default_timeout=20).run()
at.segmented_control(key="status_filter").set_value("Нет ответа").run()
assert not at.exception
assert len([b for b in at.button if b.label == "Открыть"]) == 1
assert "Из них только инфо: 1" in _html(at)
assert 'mt-dim">—' in _html(at)
def test_card_ack_posts_and_marks_alert(api):
@@ -103,6 +177,8 @@ def test_api_down_shows_banner(monkeypatch):
at = AppTest.from_file(LIST_PAGE, default_timeout=20).run()
assert not at.exception
assert "API недоступен" in _html(at)
assert any(b.label == "Повторить загрузку" for b in at.button)
assert "Всего пациентов" not in _html(at) # failed loading is not a zero-patient cabinet
def test_connect_flow_shows_code(api):