"""The page and the node are one contract, checked here rather than by eye. `ui.py` translates for a console it cannot import, and the page reads telemetry by key. Nothing in either language catches a rename, so a typo would surface as a dead button on a machine that can move — which is the wrong place to find it. """ import json import re from pathlib import Path from dora_teleop import ui PAGE = Path(__file__).resolve().parent.parent / "web" / "index.html" def page(): return PAGE.read_text() def sent_types(): """Return every message type the page can send.""" return set(re.findall(r'type:\s*"([a-z_]+)"', page())) def read_keys(): """Return the telemetry keys the page renders by name.""" listed = re.search(r"const KEYS = \[(.*?)\]", page(), re.DOTALL).group(1) return set(re.findall(r'"([a-z_]+)"', listed)) def snapshot_keys(): view = ui.ArmView() view.pose = [1.0] * 6 return set(ui.snapshot(view, "web", False, 0.0)) def test_every_message_the_page_sends_is_one_the_node_serves(): # `data` is the bridge's own envelope, not a console command. for kind in sent_types() - {"data"}: assert ui.command_of({"type": kind}) is not None or kind == "jog", kind def test_every_telemetry_key_the_page_reads_is_one_the_node_sends(): missing = read_keys() - snapshot_keys() assert not missing, f"the page reads keys the node does not send: {sorted(missing)}" def test_the_page_reads_the_pose_units_from_the_node(): # Rather than assuming mm, which is only true of one robot. assert "units.position" in page().replace("telemetry.units && telemetry.units.position", "units.position") assert "units" in snapshot_keys() def test_the_jog_the_page_sends_is_normalised_and_accepted(): # Six axes, each a fraction of full deflection. If the page ever sent a # velocity instead, it would have to know the robot's units. command = ui.command_of(json.loads('{"type": "jog", "axes": [0, -0.6, 0, 0, 0, 0]}')) assert command is not None assert command.axes == [0.0, -0.6, 0.0, 0.0, 0.0, 0.0] def test_the_page_can_ask_for_everything_the_node_serves(): # `move` is the one command with no button: it is a pose in the robot's own # units, which is exactly what this page is built not to know. served = {"jog", "jog_stop", "estop", "reset", "home", "control", "gripper", "freedrive"} assert served <= sent_types() def test_the_page_gates_its_jog_pad_on_holding_the_lease(): # The node refuses a jog from a non-owner and answers with a toast; a pad # that looks live but is refused teaches an operator to distrust the screen. assert 'owner === "web"' in page() def test_the_published_schemas_do_not_depend_on_the_value_sent(): # pyarrow reads a null `value` as its own type, so an inferred schema would # change the type of the `command` port between one verb and the next. from dora_teleop import message assert message.pack_command("stop", None).type == message.COMMAND assert message.pack_command("freedrive", 1.0).type == message.COMMAND assert ( message.pack_twist({"linear": [0.0] * 3, "angular": [0.0] * 3, "frame": "base"}).type == message.TWIST ) assert message.pack_jog([0.0] * 6).type == message.JOG