1+ """Regression tests for the AHBG presentation-only boundary.
2+
3+ Usage guidance:
4+ Focused: ``python -m unittest ahbg.presentation.tests.test_presentation``
5+ Repo discovery: ``python -m unittest discover -s ahbg/presentation/tests -p 'test*.py'``
6+ """
7+
18from __future__ import annotations
29
310import copy
411import json
12+ import math
513import sys
614import unittest
715from pathlib import Path
1018if str (ROOT ) not in sys .path :
1119 sys .path .insert (0 , str (ROOT ))
1220
13- from ahbg .presentation .geometry import axial_to_xy , center_distance
21+ from ahbg .presentation .geometry import center_distance , source_to_display
1422from ahbg .presentation .project import snapshot_from_observation
1523from ahbg .presentation .snapshot import PresentationSnapshotError , load_snapshot , validate_snapshot
1624
1725
26+ UCNS_COMMIT = "1975fe70cf4e0826a8020c2da3047569e277af64"
27+ GEOMETRY_SOURCE = {
28+ "repository" : "The-Interdependency/ucns" ,
29+ "commit" : UCNS_COMMIT ,
30+ "module" : "src/ucns/mobius_seed.py" ,
31+ "schema_id" : "ucns.mobius-seed-of-life" ,
32+ "schema_version" : "0.1.0" ,
33+ "projection_id" : "seed-of-life-seven-equal-circles" ,
34+ "selection_effect" : "none" ,
35+ }
36+
37+
1838class PresentationTests (unittest .TestCase ):
1939 def setUp (self ) -> None :
2040 self .sample = dict (load_snapshot ())
2141
22- def test_sample_is_valid (self ) -> None :
42+ def test_sample_is_valid_and_pins_ucns_source (self ) -> None :
2343 self .assertEqual (self .sample ["standing" ], "not-mechanics" )
44+ self .assertEqual (self .sample ["geometry_source" ], GEOMETRY_SOURCE )
2445 self .assertEqual (self .sample ["motions" ][0 ]["to" ], self .sample ["units" ][0 ]["tile" ])
46+ self .assertEqual ({tile ["source_slot" ] for tile in self .sample ["tiles" ]}, {"CENTER" , * (f"RING_{ i } " for i in range (6 ))})
2547
26- def test_adjacent_visual_centers_are_one_radius_apart (self ) -> None :
48+ def test_source_centers_are_scaled_not_reconstructed (self ) -> None :
2749 radius = 64.0
28- self .assertAlmostEqual (
29- center_distance (axial_to_xy (0 , 0 , radius ), axial_to_xy (1 , 0 , radius )),
30- radius ,
31- )
32-
33- def test_duplicate_tile_id_and_coordinate_fail_closed (self ) -> None :
34- for mutation in ("id" , "coord" ):
50+ center = source_to_display (0.0 , 0.0 , radius )
51+ ring0 = source_to_display (1.0 , 0.0 , radius )
52+ self .assertAlmostEqual (center_distance (center , ring0 ), radius )
53+ ring1 = source_to_display (0.5 , math .sqrt (3 ) / 2 , radius )
54+ self .assertAlmostEqual (center_distance (center , ring1 ), radius )
55+
56+ def test_duplicate_tile_id_source_slot_and_position_fail_closed (self ) -> None :
57+ for mutation in ("id" , "slot" , "position" ):
3558 payload = copy .deepcopy (self .sample )
3659 if mutation == "id" :
3760 payload ["tiles" ][1 ]["id" ] = payload ["tiles" ][0 ]["id" ]
61+ elif mutation == "slot" :
62+ payload ["tiles" ][1 ]["source_slot" ] = payload ["tiles" ][0 ]["source_slot" ]
3863 else :
39- payload ["tiles" ][1 ]["q " ] = payload ["tiles" ][0 ]["q " ]
40- payload ["tiles" ][1 ]["r " ] = payload ["tiles" ][0 ]["r " ]
64+ payload ["tiles" ][1 ]["x " ] = payload ["tiles" ][0 ]["x " ]
65+ payload ["tiles" ][1 ]["y " ] = payload ["tiles" ][0 ]["y " ]
4166 with self .assertRaises (PresentationSnapshotError ):
4267 validate_snapshot (payload )
4368
44- def test_bad_unit_tile_and_label_fail_closed (self ) -> None :
69+ def test_unknown_fields_and_bad_unit_fields_fail_closed (self ) -> None :
70+ payload = copy .deepcopy (self .sample )
71+ payload ["private_prompt" ] = "must not cross boundary"
72+ with self .assertRaisesRegex (PresentationSnapshotError , "undeclared fields" ):
73+ validate_snapshot (payload )
74+
75+ payload = copy .deepcopy (self .sample )
76+ payload ["feed" ][0 ]["dm_state" ] = "secret"
77+ with self .assertRaisesRegex (PresentationSnapshotError , "undeclared fields" ):
78+ validate_snapshot (payload )
79+
4580 payload = copy .deepcopy (self .sample )
4681 payload ["units" ][0 ]["tile" ] = []
4782 with self .assertRaises (PresentationSnapshotError ):
@@ -54,42 +89,70 @@ def test_bad_unit_tile_and_label_fail_closed(self) -> None:
5489
5590 def test_motion_destination_must_equal_presented_unit_tile (self ) -> None :
5691 payload = copy .deepcopy (self .sample )
57- payload ["motions" ][0 ]["to" ] = "e "
92+ payload ["motions" ][0 ]["to" ] = "RING_1 "
5893 with self .assertRaisesRegex (PresentationSnapshotError , "does not match" ):
5994 validate_snapshot (payload )
6095
61- def test_projector_uses_package_relative_snapshot_and_validates (self ) -> None :
96+ payload = copy .deepcopy (self .sample )
97+ payload ["motions" ] = False
98+ with self .assertRaisesRegex (PresentationSnapshotError , "must be a list" ):
99+ validate_snapshot (payload )
100+
101+ def test_projector_drops_internal_observation_and_feed_fields (self ) -> None :
62102 observation = {
63103 "turn" : 2 ,
104+ "geometry_source" : {** GEOMETRY_SOURCE , "internal_note" : "drop me" },
64105 "tiles" : [
65- {"tile_id" : "c " , "q " : 0 , "r " : 0 },
66- {"tile_id" : "ne " , "q " : 1 , "r " : - 1 },
106+ {"tile_id" : "CENTER " , "ucns_slot " : "CENTER" , "x" : 0.0 , "y " : 0.0 , "private" : "drop" },
107+ {"tile_id" : "RING_0 " , "ucns_slot " : "RING_0" , "x " : 1.0 , "y" : 0.0 },
67108 ],
68- "units" : [{"unit_id" : "A0" , "tile_id" : "ne " , "label" : "A0" }],
109+ "units" : [{"unit_id" : "A0" , "tile_id" : "RING_0 " , "label" : "A0" , "private" : "drop " }],
69110 "seed" : "must-not-leak" ,
70111 }
71112 move = {
72113 "kind" : "move" ,
73- "data" : {"unit_id" : "A0" , "from_tile_id" : "c " , "to_tile_id" : "ne " },
114+ "data" : {"unit_id" : "A0" , "from_tile_id" : "CENTER " , "to_tile_id" : "RING_0" , "dm" : "drop " },
74115 }
75116 projected = snapshot_from_observation (
76- observation , plane_id = "plane-0" , move_events = [move ]
117+ observation ,
118+ plane_id = "plane-0" ,
119+ move_events = [move ],
120+ feed = [{"turn" : 2 , "text" : "public" , "private_prompt" : "must-not-leak" }],
77121 )
78122 self .assertNotIn ("seed" , projected )
79- self .assertEqual (projected ["motions" ], [{"unit" : "A0" , "from" : "c" , "to" : "ne" }])
123+ self .assertNotIn ("internal_note" , projected ["geometry_source" ])
124+ self .assertEqual (projected ["feed" ], [{"turn" : 2 , "text" : "public" }])
125+ self .assertEqual (projected ["motions" ], [{"unit" : "A0" , "from" : "CENTER" , "to" : "RING_0" }])
80126
81- def test_browser_source_contains_accessibility_and_layer_guards (self ) -> None :
127+ def test_browser_source_contains_accessibility_and_validation_guards (self ) -> None :
82128 source = (ROOT / "ahbg" / "presentation" / "board.js" ).read_text (encoding = "utf-8" )
129+ html = (ROOT / "ahbg" / "presentation" / "board.html" ).read_text (encoding = "utf-8" )
83130 for phrase in (
84131 'hit.setAttribute("tabindex", "0")' ,
85132 'event.key === "Enter" || event.key === " "' ,
86- "tile id repeats" ,
87- "q,r must be integers " ,
133+ "UCNS source slot repeats" ,
134+ "motions must be a list when present " ,
88135 "motion destination for" ,
89136 'selectionRing.setAttribute("class", "selection-ring")' ,
90- "tileGroups" ,
137+ "minimumChord" ,
138+ "sourceToPixel" ,
91139 ):
92140 self .assertIn (phrase , source )
141+ self .assertIn ('role="group"' , html )
142+ self .assertNotIn ('role="img"' , html )
143+ self .assertNotIn ("axialToPixel" , source )
144+
145+ def test_behavior_modules_declare_module_build (self ) -> None :
146+ for relative in (
147+ "ahbg/presentation/__init__.py" ,
148+ "ahbg/presentation/snapshot.py" ,
149+ "ahbg/presentation/project.py" ,
150+ "ahbg/presentation/geometry.py" ,
151+ "ahbg/presentation/board.js" ,
152+ ):
153+ text = (ROOT / relative ).read_text (encoding = "utf-8" )
154+ self .assertIn ("=== MODULE_BUILD ===" , text , relative )
155+ self .assertIn ("=== END MODULE_BUILD ===" , text , relative )
93156
94157 def test_sample_json_is_plain_data (self ) -> None :
95158 path = ROOT / "ahbg" / "presentation" / "sample_snapshot.json"
0 commit comments