"""Desktop channel navigation and fixed-size manual saves.""" from __future__ import annotations import os import unittest from unittest import mock import numpy as np os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") from PyQt6.QtCore import QSignalBlocker # noqa: E402 from PyQt6.QtWidgets import QApplication, QComboBox, QLineEdit, QPushButton # noqa: E402 from python_app.tests.gui_test_support import isolated_window, widget from python_app.models.dataset_model import ComboKey, ResultBlock, ResultCollection, ResultPayload, SweepCollection # noqa: E402 from python_app.models.run_config_schema import RunConfigModel # noqa: E402 class ComboNavigationTest(unittest.TestCase): @classmethod def setUpClass(cls) -> None: cls.app = QApplication.instance() or QApplication([]) cls.window = isolated_window(cls.addClassCleanup) cls.window._pipeline.timer.stop() cls.window._pipeline.replay.timer.stop() def setUp(self) -> None: self.w = self.window self.root = self.w._profile.state.path.parent self.mode = self.w._view.processing.mode self.single_input = self.w._view.switches.single_input self.single_output = self.w._view.switches.single_output self.next_button = self.w._view.switches.next_combo self.w._profile.state.config = RunConfigModel.load_from_path(self.w._profile.state.path) self.w._radar.set_combo_mode("text") with QSignalBlocker(self.mode): self.mode.setCurrentText("bscan") self.w._pipeline.reset_history() self.w._plots.bscan.clear() self.single_input.setText("0") self.single_output.setText("0") def _seed_bscan(self, pairs: tuple[tuple[int, int], ...] | list[tuple[int, int]] = ((0, 0), (0, 1))) -> None: result = ResultCollection(1, 1, capture_start_ns=1, blocks=[ ResultBlock(ComboKey(*key), [ResultPayload( processing_name="bscan", kind=1, frequency_hz=np.array([0.0, 1.0], dtype=np.float32), trace=np.array([key[1] + 1, key[1] + 2], dtype=np.complex64), )]) for key in pairs ]) self.w._history.results.append(result) self.w._plots.draw(result) def test_next_button_draws_cached_combo_without_changing_acquisition(self) -> None: for model in ("librevna", "librevna_multi", "kamil_adc"): with self.subTest(model=model): self.w._profile.state.config.radar.model = model self._seed_bscan() self.w._plots.select_combo(0, 0) with mock.patch.object(self.w._live, "publish") as write_config: self.next_button.click() self.assertEqual(self.w._plots.display_key, (0, 1)) self.assertEqual(self.single_input.text(), "0") self.assertEqual(self.single_output.text(), "0") plot = self.w._plots.bscan.widget.getPlotItem() assert plot is not None self.assertIn("in0/out1", plot.titleLabel.text) write_config.assert_not_called() self.assertFalse(self.w._pipeline.replay.timer.isActive()) def test_navigation_wraps_only_through_recorded_pairs(self) -> None: keys = [(0, 0), (0, 3), (2, 1)] self._seed_bscan(keys) self.w._plots.select_combo(*keys[0]) visited = [self.w._plots.display_key] for _ in keys: self.next_button.click() visited.append(self.w._plots.display_key) self.assertEqual(visited, keys + [keys[0]]) with self.assertRaisesRegex(ValueError, "Unknown channel combination"): self.w._plots.select_combo(999, 999) self.assertEqual(self.w._plots.display_key, keys[0]) def _seed_unaligned_stages(self) -> tuple[list[SweepCollection], list[SweepCollection], list[ResultCollection]]: raw = [SweepCollection(i, i, capture_start_ns=i) for i in range(1, 1003)] pre = [SweepCollection(i, i, capture_start_ns=i) for i in range(11, 1012)] results = [ResultCollection(i, i, capture_start_ns=i) for i in range(21, 1021)] self.w._pipeline.reset_history() self.w._history.raw.extend(raw) self.w._history.preprocessed.extend(pre) self.w._history.results.extend(results) return raw[-1000:], pre[-1000:], results[-1000:] def test_save_cancels_when_drain_restarts_acquisition_with_another_profile(self) -> None: self._seed_unaligned_stages() active = self.w._pipeline.active_config self.addCleanup(setattr, self.w._pipeline, "active_config", active) with mock.patch.object(self.w._pipeline, "drain_snapshot", side_effect=lambda: setattr( self.w._pipeline, "active_config", RunConfigModel(), )), mock.patch.object(self.w._snapshots.store, "save_runtime_snapshot_numpy") as save: self.w._snapshots.save() save.assert_not_called() if __name__ == "__main__": unittest.main()