228 lines
6.4 KiB
Python
228 lines
6.4 KiB
Python
import types
|
|
from collections import namedtuple
|
|
|
|
import pytest
|
|
|
|
from target_simulator.gui import trajectory_editor_window as tew
|
|
|
|
|
|
class DummyVar:
|
|
def __init__(self, value=False):
|
|
self._value = value
|
|
|
|
def get(self):
|
|
return self._value
|
|
|
|
def set(self, v):
|
|
self._value = v
|
|
|
|
|
|
class FakeTree:
|
|
def __init__(self):
|
|
self.items = {}
|
|
self._focus = ""
|
|
|
|
def delete(self, *args, **kwargs):
|
|
self.items.clear()
|
|
|
|
def get_children(self):
|
|
return list(self.items.keys())
|
|
|
|
def insert(self, parent, pos, iid=None, values=None):
|
|
self.items[iid] = values
|
|
|
|
def see(self, item_id):
|
|
pass
|
|
|
|
def focus(self):
|
|
return self._focus
|
|
|
|
def focus_set(self, v):
|
|
self._focus = v
|
|
|
|
def selection_set(self, v):
|
|
self._focus = v
|
|
|
|
def config(self, **kwargs):
|
|
pass
|
|
|
|
|
|
class DummyLabel:
|
|
def __init__(self):
|
|
self.text = ""
|
|
|
|
def config(self, **kwargs):
|
|
self.text = kwargs.get("text", self.text)
|
|
|
|
|
|
def _make_window():
|
|
# Create instance without running __init__ (which blocks on wait_window)
|
|
inst = tew.TrajectoryEditorWindow.__new__(tew.TrajectoryEditorWindow)
|
|
inst.existing_ids = []
|
|
inst.result_target = None
|
|
inst.initial_max_range = 10
|
|
inst.time_multiplier = 1.0
|
|
inst.total_sim_time = 0.0
|
|
inst.waypoints = []
|
|
inst.use_spline_var = DummyVar(False)
|
|
inst.gui_update_queue = None
|
|
inst.preview_engine = None
|
|
inst.is_preview_running = DummyVar(False)
|
|
inst.is_paused = DummyVar(False)
|
|
inst.wp_tree = FakeTree()
|
|
inst.ppi_preview = types.SimpleNamespace(draw_trajectory_preview=lambda **k: None, update_targets=lambda u: None)
|
|
inst.sim_progress_var = DummyVar(0.0)
|
|
inst.sim_time_label = DummyLabel()
|
|
inst.time_multiplier_var = types.SimpleNamespace(get=lambda: "1x")
|
|
inst.multiplier_combo = types.SimpleNamespace(config=lambda **k: None)
|
|
inst.play_button = types.SimpleNamespace(config=lambda **k: None)
|
|
inst.pause_button = types.SimpleNamespace(config=lambda **k: None)
|
|
inst.stop_button = types.SimpleNamespace(config=lambda **k: None)
|
|
return inst
|
|
|
|
|
|
def test_on_spline_toggle_shows_info_when_too_few_waypoints(monkeypatch):
|
|
inst = _make_window()
|
|
inst.waypoints = [1, 2] # fewer than 4
|
|
inst.use_spline_var.set(True)
|
|
|
|
called = {}
|
|
|
|
def fake_showinfo(title, message, parent=None):
|
|
called["title"] = title
|
|
called["message"] = message
|
|
|
|
monkeypatch.setattr(tew.messagebox, "showinfo", fake_showinfo)
|
|
inst._on_spline_toggle()
|
|
assert called.get("title") == "Spline Not Available"
|
|
assert inst.use_spline_var.get() is False
|
|
|
|
|
|
def test_on_time_multiplier_changed_valid_and_invalid():
|
|
inst = _make_window()
|
|
|
|
inst.time_multiplier_var = types.SimpleNamespace(get=lambda: "2x")
|
|
inst.preview_engine = None
|
|
inst._on_time_multiplier_changed()
|
|
assert inst.time_multiplier == 2.0
|
|
|
|
inst.time_multiplier_var = types.SimpleNamespace(get=lambda: "bad")
|
|
inst._on_time_multiplier_changed()
|
|
assert inst.time_multiplier == 1.0
|
|
|
|
|
|
def test_on_ok_invalid_first_waypoint_shows_error(monkeypatch):
|
|
inst = _make_window()
|
|
inst.waypoints = []
|
|
|
|
captured = {}
|
|
|
|
def fake_showerror(title, message, parent=None):
|
|
captured["title"] = title
|
|
captured["message"] = message
|
|
|
|
monkeypatch.setattr(tew.messagebox, "showerror", fake_showerror)
|
|
inst._on_ok()
|
|
assert captured.get("title") == "Invalid Trajectory"
|
|
|
|
|
|
def test_on_remove_waypoint_no_selection_and_single_waypoint(monkeypatch):
|
|
inst = _make_window()
|
|
|
|
# No selection
|
|
inst.wp_tree._focus = ""
|
|
captured = {}
|
|
|
|
def fake_showwarning(title, message, parent=None):
|
|
captured["warn"] = (title, message)
|
|
|
|
monkeypatch.setattr(tew.messagebox, "showwarning", fake_showwarning)
|
|
inst._on_remove_waypoint()
|
|
assert captured.get("warn")[0] == "No Selection"
|
|
|
|
# Single waypoint present -> error on remove
|
|
inst.wp_tree._focus = "0"
|
|
inst.waypoints = [object()]
|
|
|
|
def fake_showerror(title, message, parent=None):
|
|
captured["err"] = (title, message)
|
|
|
|
monkeypatch.setattr(tew.messagebox, "showerror", fake_showerror)
|
|
inst._on_remove_waypoint()
|
|
assert captured.get("err")[0] == "Action Denied"
|
|
|
|
|
|
def test_populate_waypoint_list_and_get_total_time(monkeypatch):
|
|
inst = _make_window()
|
|
|
|
# Create dummy waypoint objects matching expected attributes
|
|
WP = namedtuple(
|
|
"WP",
|
|
[
|
|
"maneuver_type",
|
|
"target_range_nm",
|
|
"target_azimuth_deg",
|
|
"target_altitude_ft",
|
|
"target_velocity_fps",
|
|
"target_heading_deg",
|
|
"duration_s",
|
|
"longitudinal_acceleration_g",
|
|
"lateral_acceleration_g",
|
|
"vertical_acceleration_g",
|
|
"turn_direction",
|
|
],
|
|
)
|
|
|
|
wp1 = WP(
|
|
maneuver_type=tew.ManeuverType.FLY_TO_POINT,
|
|
target_range_nm=10.0,
|
|
target_azimuth_deg=90.0,
|
|
target_altitude_ft=1000.0,
|
|
target_velocity_fps=0.0,
|
|
target_heading_deg=0.0,
|
|
duration_s=0.0,
|
|
longitudinal_acceleration_g=0.0,
|
|
lateral_acceleration_g=0.0,
|
|
vertical_acceleration_g=0.0,
|
|
turn_direction=tew.TurnDirection.LEFT,
|
|
)
|
|
|
|
wp2 = WP(
|
|
maneuver_type=tew.ManeuverType.FLY_FOR_DURATION,
|
|
target_range_nm=0,
|
|
target_azimuth_deg=0,
|
|
target_altitude_ft=0,
|
|
target_velocity_fps=100.0,
|
|
target_heading_deg=45.0,
|
|
duration_s=12.5,
|
|
longitudinal_acceleration_g=0.0,
|
|
lateral_acceleration_g=0.0,
|
|
vertical_acceleration_g=0.0,
|
|
turn_direction=tew.TurnDirection.LEFT,
|
|
)
|
|
|
|
inst.waypoints = [wp1, wp2]
|
|
inst._populate_waypoint_list()
|
|
|
|
# Verify tree has two items inserted
|
|
assert len(inst.wp_tree.items) == 2
|
|
|
|
# Test _get_total_simulation_time by monkeypatching Target.generate_path_from_waypoints
|
|
def fake_generate(wps, use_spline):
|
|
return ([], 123.4)
|
|
|
|
monkeypatch.setattr(tew.Target, "generate_path_from_waypoints", staticmethod(fake_generate))
|
|
inst.use_spline_var = DummyVar(False)
|
|
total = inst._get_total_simulation_time()
|
|
assert total == 123.4
|
|
import pytest
|
|
import tkinter as tk
|
|
from target_simulator.gui.trajectory_editor_window import TrajectoryEditorWindow
|
|
|
|
|
|
def test_trajectory_editor_window_init():
|
|
root = tk.Tk()
|
|
win = TrajectoryEditorWindow(root, existing_ids=[], max_range_nm=100)
|
|
assert win is not None
|
|
root.destroy()
|