"""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 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 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"),
*(APP / "moduly" / "dodatki" / "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():
""" 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 "
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 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 —
zostają w rysunku i nic w chartwheel.py się nie zmienia."""
wheel = (APP / "chartwheel.py").read_text(encoding="utf-8")
assert "" in wheel
assert "wheeltip" not in wheel