"""Konwersja tekst ↔ symbol astrologiczny (LOG-22). Sedno: glify muszą być KOMPLETNE (każdy obiekt/znak/aspekt, który liczymy, ma symbol), DWUKIERUNKOWE (symbol → tekst z powrotem) i TEKSTOWE, nigdy emoji (DAN-18) — inaczej na kosmogramie wyjdą kolorowe kwadraty zamiast czarno-białych symboli. """ import pytest from app.engine import glyphs as G from app.engine.aspects import MAJOR from app.engine.formats import SIGNS from app.engine.models import DEFAULT_OBJECTS VS15, VS16 = "︎", "️" def _all_glyphs(): return (list(G.PLANET.values()) + list(G.POINT.values()) + list(G.LOT.values()) + list(G.SIGN.values()) + list(G.ASPECT.values()) + [G.RETROGRADE]) # ------------------------------------------------------------- kompletność def test_every_default_object_has_a_glyph(): """Każdy obiekt z kanonicznego zestawu musi mieć symbol — inaczej na kole zostanie dziura.""" missing = [name for name in DEFAULT_OBJECTS if G.glyph_for(name) is None] assert not missing, f"obiekty bez glifu: {missing}" def test_every_sign_has_a_glyph(): missing = [s for s in SIGNS if G.sign_glyph(s) is None] assert not missing, f"znaki bez glifu: {missing}" def test_every_major_aspect_has_a_glyph(): missing = [a for a in MAJOR if G.aspect_glyph(a) is None] assert not missing, f"aspekty główne bez glifu: {missing}" def test_fortune_and_retrograde_from_requirement(): """Wymóg wprost wymienia ⊗ (Fortuna) i ℞ (retrogradacja).""" assert G.glyph_for("Fortune") == "⊗" assert G.RETROGRADE == "℞" # ------------------------------------------------------ DAN-18: tekst, nie emoji def test_no_glyph_uses_emoji_variation_selector(): """VS16 (U+FE0F) wymusiłby prezentację emoji — nie może go być NIGDZIE.""" offenders = [g for g in _all_glyphs() if VS16 in g] assert not offenders, f"glify z wariantem emoji (FE0F): {offenders!r}" def test_signs_force_text_presentation(): """Znaki zodiaku mają domyślnie wariant emoji → MUSZĄ nieść VS15.""" for s in SIGNS: assert VS15 in G.sign_glyph(s), f"{s} bez wymuszenia tekstu (VS15)" def test_venus_mars_force_text_but_sun_does_not(): """♀/♂ mają wariant emoji (VS15 konieczny); ☉ nie ma (VS15 zbędny).""" assert VS15 in G.glyph_for("Venus") assert VS15 in G.glyph_for("Mars") assert VS15 not in G.glyph_for("Sun") # bez zaśmiecania niepotrzebnym selektorem # ---------------------------------------------------------- dwukierunkowość def test_object_round_trip(): for name in DEFAULT_OBJECTS: assert G.name_for_glyph(G.glyph_for(name)) == name def test_sign_round_trip(): for s in SIGNS: assert G.sign_for_glyph(G.sign_glyph(s)) == s def test_aspect_round_trip(): for a in MAJOR: assert G.aspect_for_glyph(G.aspect_glyph(a)) == a def test_reverse_tolerates_missing_variant_selector(): """Symbol wklejony bez VS15 też musi się rozpoznać — bo z zewnątrz przyjdzie dowolna forma (DAN-17: łańcuch znosi każdy Unicode).""" assert G.sign_for_glyph("♓") == "Pisces" # ♓ bez VS15 assert G.sign_for_glyph("♓" + VS15) == "Pisces" # ♓ z VS15 assert G.name_for_glyph("♀") == "Venus" # ♀ bez VS15 # --------------------------------------------------------- glifikacja tekstu def test_glyphify_matches_requirement_example(): """Przykład z wymagań: [Sa [Pis 26°08' [conj [PF -> ♄ ♓ 26°08' ☌ ⊗.""" out = G.glyphify("[Sa [Pis 26°08' [conj [PF") assert G.glyph_for("Saturn") in out assert G.sign_glyph("Pisces") in out assert G.aspect_glyph("conjunction") in out assert G.glyph_for("Fortune") in out assert "26°08'" in out # stopnie nietknięte def test_glyphify_handles_retrograde_and_leaves_unknown(): out = G.glyphify("[Ma Rx w [Xyz") assert G.glyph_for("Mars") in out assert G.RETROGRADE in out assert "[Xyz" in out # nieznany token bez zmian def test_glyphify_empty_is_safe(): assert G.glyphify("") == ""