""" test_main_window.py — Tests for the GUI skeleton (Step 1). TESTING PyQt6 APPLICATIONS: ============================ GUI testing has a challenge: Qt widgets need a running QApplication. We use a pytest fixture to create ONE QApplication for the entire test session. Key things we verify: 1. Window creates without errors 2. All panels exist and are the correct widget types 3. Tables have the right columns 4. Buttons and controls are in the expected initial state 5. Window has a reasonable minimum size We do NOT test visual appearance (that's manual testing). We DO test that the widget tree is correctly assembled. """ import sys import pytest # Add src to path so we can import our modules sys.path.insert(0, str(__import__("pathlib").Path(__file__).parent.parent / "src")) from PyQt6.QtWidgets import QApplication, QDockWidget, QTreeWidget from PyQt6.QtCore import Qt # ─── Fixtures ────────────────────────────────────────────────────────── # Fixtures are pytest's way of providing shared setup. The "session" scope # means this QApplication is created once and reused across ALL tests. @pytest.fixture(scope="session") def app(): """Create a QApplication instance for the test session.""" application = QApplication.instance() or QApplication(sys.argv) yield application @pytest.fixture def window(app): """Create a fresh MainWindow for each test.""" from main_window import MainWindow w = MainWindow() yield w w.close() # ─── Tests ───────────────────────────────────────────────────────────── class TestWindowBasics: """Test that the window initializes correctly.""" def test_window_title(self, window): assert window.windowTitle() == "LIN Simulator" def test_minimum_size(self, window): assert window.minimumWidth() >= 1024 assert window.minimumHeight() >= 768 def test_central_widget_exists(self, window): assert window.centralWidget() is not None class TestMenuBar: """Test menu bar structure.""" def test_menu_bar_exists(self, window): assert window.menuBar() is not None def test_load_ldf_action_exists(self, window): assert window.action_load_ldf is not None assert window.action_load_ldf.text() == "&Load LDF..." def test_load_ldf_shortcut(self, window): assert window.action_load_ldf.shortcut().toString() == "Ctrl+O" class TestLdfToolbar: """Test the LDF file toolbar.""" def test_ldf_path_field_exists(self, window): assert window.ldf_path_edit is not None assert window.ldf_path_edit.isReadOnly() def test_ldf_path_placeholder(self, window): assert window.ldf_path_edit.placeholderText() == "No LDF file loaded" def test_browse_button_exists(self, window): assert window.btn_browse_ldf is not None def test_auto_reload_default_checked(self, window): assert window.chk_auto_reload.isChecked() class TestTxTable: """Test the Tx (transmit) tree widget structure.""" def test_tx_table_exists(self, window): assert isinstance(window.tx_table, QTreeWidget) def test_tx_table_columns(self, window): assert window.tx_table.columnCount() == 6 headers = [] for i in range(window.tx_table.columnCount()): headers.append(window.tx_table.headerItem().text(i)) assert headers == [ "Name", "ID / Bit", "Length / Width", "Interval (ms)", "Value", "Action" ] def test_tx_table_alternating_colors(self, window): assert window.tx_table.alternatingRowColors() def test_tx_table_is_decorated(self, window): """Tree should show expand/collapse arrows.""" assert window.tx_table.rootIsDecorated() class TestRxTable: """Test the Rx (receive) tree widget structure.""" def test_rx_table_exists(self, window): assert isinstance(window.rx_table, QTreeWidget) def test_rx_table_columns(self, window): assert window.rx_table.columnCount() == 5 headers = [] for i in range(window.rx_table.columnCount()): headers.append(window.rx_table.headerItem().text(i)) assert headers == ["Timestamp", "Name", "ID / Bit", "Length / Width", "Value"] def test_rx_table_is_decorated(self, window): """Tree should show expand/collapse arrows.""" assert window.rx_table.rootIsDecorated() class TestConnectionDock: """Test the connection panel dock widget.""" def test_dock_exists(self, window): docks = window.findChildren(QDockWidget) assert len(docks) == 1 assert docks[0].windowTitle() == "Connection" def test_device_combo_exists(self, window): assert window.combo_device is not None def test_connect_button_exists(self, window): assert window.btn_connect is not None assert window.btn_connect.isEnabled() def test_disconnect_button_disabled_initially(self, window): """Disconnect should be disabled when no device is connected.""" assert not window.btn_disconnect.isEnabled() def test_status_label_shows_disconnected(self, window): assert "Disconnected" in window.lbl_conn_status.text() def test_baud_rate_label_exists(self, window): assert window.lbl_baud_rate is not None def test_baud_rate_shows_placeholder_before_ldf(self, window): """Before loading an LDF, baud rate should show a placeholder.""" assert "load LDF" in window.lbl_baud_rate.text() class TestControlBar: """Test the bottom control toolbar.""" def test_schedule_combo_exists(self, window): assert window.combo_schedule is not None def test_scheduler_buttons_disabled_initially(self, window): """Start/Stop/Pause should be disabled until LDF loaded + connected.""" assert not window.btn_start.isEnabled() assert not window.btn_stop.isEnabled() assert not window.btn_pause.isEnabled() def test_manual_send_disabled_initially(self, window): assert not window.btn_manual_send.isEnabled() def test_global_rate_spinbox_exists(self, window): assert window.spin_global_rate is not None def test_global_rate_default_50ms(self, window): """50ms default = ~20 frames/sec, typical LIN schedule speed.""" assert window.spin_global_rate.value() == 50 def test_global_rate_range(self, window): """Range should be 1ms (fast stress test) to 10000ms (very slow).""" assert window.spin_global_rate.minimum() == 1 assert window.spin_global_rate.maximum() == 10000 def test_global_rate_suffix(self, window): """Should display the unit 'ms' in the spinbox.""" assert window.spin_global_rate.suffix() == " ms" class TestStatusBar: """Test the status bar.""" def test_status_bar_exists(self, window): assert window.statusBar() is not None def test_connection_status_label(self, window): assert "Disconnected" in window.lbl_status_connection.text()