feat(topology): render temporal graph from database
This commit is contained in:
@@ -1,114 +1,44 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
HTML_PATH = ROOT / "data" / "wiki" / "guanghan_topology_demo.html"
|
||||
TIMELINE_PATH = ROOT / "user_input" / "广寒计划" / "Timeline.md"
|
||||
|
||||
CLICKABLE_MODULES = {
|
||||
"core",
|
||||
"habitat-1",
|
||||
"habitat-2",
|
||||
"greenhouse",
|
||||
"laboratory",
|
||||
"power-node",
|
||||
"power-north",
|
||||
"power-east",
|
||||
"power-south",
|
||||
"power-west",
|
||||
"wugang-2",
|
||||
"binglun-1",
|
||||
"binglun-2",
|
||||
"binglun-3",
|
||||
}
|
||||
|
||||
|
||||
class GuanghanTopologySourceTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.html = HTML_PATH.read_text(encoding="utf-8")
|
||||
timeline = TIMELINE_PATH.read_text(encoding="utf-8")
|
||||
cls.ghc05 = timeline.split("GHC-05任务", 1)[1].split("JC-02任务", 1)[0]
|
||||
|
||||
def test_clickable_module_inventory_is_exact(self) -> None:
|
||||
found = set(re.findall(r'data-module="([^"]+)"', self.html))
|
||||
self.assertEqual(found, CLICKABLE_MODULES)
|
||||
def test_topology_is_rendered_from_snapshot_layout(self) -> None:
|
||||
self.assertNotIn('<g class="topology-node"', self.html)
|
||||
self.assertNotIn("GROUP_MEMBERS", self.html)
|
||||
self.assertNotIn("data-conn=", self.html)
|
||||
self.assertIn("currentData.layout", self.html)
|
||||
self.assertIn("currentData.connections", self.html)
|
||||
self.assertIn("function renderTopology", self.html)
|
||||
|
||||
def test_connection_inventory_matches_approved_topology(self) -> None:
|
||||
self.assertEqual(self.html.count('data-connection="pressurized"'), 2)
|
||||
self.assertIn('data-link="wugang-2--greenhouse"', self.html)
|
||||
self.assertIn('data-link="habitat-1--power-north"', self.html)
|
||||
self.assertNotIn("perimeter-corridor", self.html)
|
||||
def test_radiators_are_dynamic_layout_decorations(self) -> None:
|
||||
self.assertNotIn('<g class="radiator">', self.html)
|
||||
self.assertIn("decorations.radiator", self.html)
|
||||
self.assertIn("function createRadiator", self.html)
|
||||
|
||||
def test_walkways_and_radiators_are_not_clickable(self) -> None:
|
||||
self.assertNotRegex(
|
||||
self.html,
|
||||
r'<(?:line|path|rect)[^>]+data-connection="pressurized"[^>]+data-module=',
|
||||
)
|
||||
self.assertNotRegex(
|
||||
self.html,
|
||||
r'<(?:g|rect)[^>]+class="[^"]*radiator[^"]*"[^>]+data-module=',
|
||||
)
|
||||
|
||||
def test_each_radiator_has_a_visible_text_label(self) -> None:
|
||||
radiator_groups = re.findall(
|
||||
r'<g class="radiator"[^>]*>(.*?)</g>',
|
||||
self.html,
|
||||
flags=re.S,
|
||||
)
|
||||
self.assertEqual(len(radiator_groups), 4)
|
||||
for group in radiator_groups:
|
||||
self.assertIn(">散热器</text>", group)
|
||||
rect = re.search(r'<rect x="(\d+)" y="(\d+)" width="(\d+)"', group)
|
||||
label = re.search(r'<text[^>]+x="(\d+)" y="(\d+)"[^>]*>散热器</text>', group)
|
||||
self.assertIsNotNone(rect)
|
||||
self.assertIsNotNone(label)
|
||||
rect_x, rect_y, rect_width = map(int, rect.groups())
|
||||
label_x, label_y = map(int, label.groups())
|
||||
self.assertGreaterEqual(label_x, rect_x)
|
||||
self.assertLessEqual(label_x, rect_x + rect_width)
|
||||
self.assertLess(label_y, rect_y)
|
||||
|
||||
def test_future_extensions_exist_only_as_source_comments(self) -> None:
|
||||
comments = "\n".join(re.findall(r"<!--(.*?)-->", self.html, flags=re.S))
|
||||
visible_source = re.sub(r"<!--.*?-->", "", self.html, flags=re.S)
|
||||
def test_future_expansion_anchors_remain_source_comments(self) -> None:
|
||||
for direction in ("north", "east", "west"):
|
||||
self.assertIn(f"FUTURE_EXPANSION_ANCHOR:{direction}", comments)
|
||||
self.assertNotIn("未来北部十字结构", visible_source)
|
||||
self.assertNotIn("未来东部十字结构", visible_source)
|
||||
self.assertNotIn("未来西部十字结构", visible_source)
|
||||
|
||||
def test_every_clickable_module_has_dimensions_field(self) -> None:
|
||||
for module_id in CLICKABLE_MODULES:
|
||||
match = re.search(
|
||||
rf'"{re.escape(module_id)}"\s*:\s*\{{(.*?)\n \}}(?:,|\n)',
|
||||
self.assertIn(
|
||||
f"FUTURE_EXPANSION_ANCHOR:{direction}",
|
||||
self.html,
|
||||
flags=re.S,
|
||||
)
|
||||
self.assertIsNotNone(match, msg=f"{module_id} detail object is missing")
|
||||
self.assertIn("dimensions:", match.group(1), msg=f"{module_id} is missing dimensions")
|
||||
self.assertNotIn("未来北部十字结构", self.html)
|
||||
self.assertNotIn("未来东部十字结构", self.html)
|
||||
self.assertNotIn("未来西部十字结构", self.html)
|
||||
|
||||
def test_timeline_contains_only_the_two_real_corridors(self) -> None:
|
||||
for incorrect in (
|
||||
"4段加压走道",
|
||||
"居住舱#1↔居住舱#2",
|
||||
"居住舱#2↔实验舱",
|
||||
"实验舱↔温室",
|
||||
"温室↔居住舱#1",
|
||||
"环形加压通路",
|
||||
"8个对接面",
|
||||
"4段加压走道48小时",
|
||||
):
|
||||
self.assertNotIn(incorrect, self.ghc05)
|
||||
self.assertIn("居住舱#1远端对接口", self.ghc05)
|
||||
self.assertIn("电力模块北部", self.ghc05)
|
||||
self.assertIn("温室远端对接口", self.ghc05)
|
||||
self.assertIn("吴刚#2", self.ghc05)
|
||||
self.assertIn("从温室模块远端接口断开", self.ghc05)
|
||||
def test_page_uses_same_origin_api(self) -> None:
|
||||
self.assertIn("const API_BASE = ''", self.html)
|
||||
self.assertNotIn("http://localhost:5000", self.html)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user