"""Dymki na kosmogramie (PRE-12/PRE-25). Rysunek niesie przy każdym obiekcie i każdej linii aspektu, ale natywne dymki przeglądarki przestały się pokazywać po dodaniu powiększania na pełne okno. Sprawdzone w przeglądarce: elementy z <title> SĄ osiągalne kursorem (19 z 20 trafień w hit-teście), więc nic ich nie zasłaniało — problem był w samym natywnym dymku. Test strukturalny, jak przy wheelzoom: bez uruchamiania przeglądarki nie zbadamy zachowania JS-a. Pilnujemy więc tego, co da się zepsuć po cichu i co ma znaczenie: kolejności warstw, zdejmowania i ODWIESZANIA <title> oraz wpięcia skryptu. Zachowanie sprawdzone ręcznie w przeglądarce przy 1280×900, w obu stanach koła. """ import pathlib import re APP = pathlib.Path(__file__).resolve().parents[1] / "app" JS = (APP / "static" / "wheeltip.js").read_text(encoding="utf-8") # Arkusz jest teraz rozbity per uprawnienie (arkusz wspólny + arkusze ekranów), # bo jeden plik ze wszystkimi selektorami był spisem funkcji programu dla # każdego, kto go pobrał. Reguły szukamy w całości — który arkusz ją niesie, # pilnuje osobny test pokrycia. CSS = "\n".join(f.read_text(encoding="utf-8") for f in sorted((APP / "static").glob("*.css"))) BASE = (APP / "templates" / "base.html").read_text(encoding="utf-8") def test_script_is_loaded_only_where_there_is_a_wheel(): """Koło jest na Horoskopie i na Skompiluj — i tylko tam ten skrypt ma prawo się pojawić. Wcześniej ładowany był z base.html, czyli na KAŻDEJ stronie. Konto, które ma wyłącznie Pliki, pobierało go przy zwykłym wejściu na swój jedyny ekran, a jego treść wymienia z nazwy zakładki, o których takie konto nie ma prawa wiedzieć — łącznie z tą, której jeszcze nie ma.""" from pathlib import Path szablony = Path(__file__).resolve().parents[1] / "app" / "templates" z_kolem, bez_kola = set(), set() for f in szablony.glob("*.html"): (z_kolem if "wheeltip.js" in f.read_text(encoding="utf-8") else bez_kola).add(f.name) assert z_kolem == {"chart.html", "compile.html"}, f"ładowany też przez: {z_kolem}" assert "files.html" in bez_kola and "logowanie.html" in bez_kola def test_tooltip_sits_above_the_zoom_overlay_but_below_the_progress_window(): """Warstwy: nakładka powiększonego koła < dymek < okno postępu. Dymek MUSI być nad nakładką, inaczej po powiększeniu — czyli dokładnie tam, gdzie zgłoszono problem — byłby niewidoczny. I POD oknem postępu, bo gdy trwa pisanie horoskopu, log operacji ma zostać na wierzchu.""" tip = re.search(r"\.wheel-tip\s*\{[^}]*z-index:\s*(\d+)", CSS) zoom = re.search(r"\.wheel-fig\.zoomed\s*\{[^}]*z-index:\s*(\d+)", CSS) progress = re.search(r"\.progress-overlay\s*\{[^}]*z-index:\s*(\d+)", CSS) assert tip and zoom and progress, "brakuje którejś reguły z-index" assert int(zoom.group(1)) < int(tip.group(1)) < int(progress.group(1)), \ f"warstwy: nakładka {zoom.group(1)}, dymek {tip.group(1)}, postęp {progress.group(1)}" def test_tooltip_never_swallows_the_pointer(): """Dymek chodzi za kursorem, więc bez pointer-events:none zasłaniałby sam sobie element, nad którym stoi — i migotał.""" rule = re.search(r"\.wheel-tip\s*\{([^}]*)\}", CSS) assert rule and "pointer-events: none" in rule.group(1) def test_title_is_detached_only_for_the_hover_and_always_put_back(): """<title> zostaje w rysunku — to nazwa dostępna dla czytników ekranu. Na czas najechania odpinamy go, żeby nie było dwóch dymków naraz, ale MUSI wrócić. Brak odwieszania okradałby czytniki ekranu po pierwszym najechaniu, i to po cichu — na ekranie nic by nie było widać.""" assert "removeChild" in JS, "dymek natywny nie jest tłumiony" assert "appendChild(stash.node)" in JS, "brak odwieszania <title>" for leaving in ("mouseleave", "scroll", "resize"): assert leaving in JS, f"brak sprzątania przy: {leaving}" def test_tooltip_reads_the_title_that_is_already_in_the_drawing(): """Źródłem tekstu jest <title> z SVG, nie druga kopia opisów w JS — inaczej rozjechałyby się przy pierwszej zmianie treści dymka.""" assert ":scope > title" in JS assert "textContent" in JS def test_pdf_path_is_untouched(): """PDF składa się po stronie serwera, więc żaden dymek go nie dotyczy — <title> zostają w rysunku i nic w chartwheel.py się nie zmienia.""" wheel = (APP / "chartwheel.py").read_text(encoding="utf-8") assert "<title>" in wheel assert "wheeltip" not in wheel