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_simulated_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()