"""Reusable Qt adapter tests, runnable with PySide2 or PySide6, without a GUI app repo.""" import os os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") import time import unittest from dataclasses import replace from unittest.mock import patch from set_devices.plot_processing import Axis, Series, Snapshot, process from set_devices.qt_ports.plot_processing import SignalProcessingPanel, PlotProcessingAttachment, QWidget try: from PySide6.QtWidgets import QApplication except ImportError: from PySide2.QtWidgets import QApplication class ProcessingQtTests(unittest.TestCase): @classmethod def setUpClass(cls): cls.app = QApplication.instance() or QApplication([]) def setUp(self): self.panel = SignalProcessingPanel() self.snapshot = Snapshot([Series("a", "A", [(0, 0), (1, 1), (2, 0)])]) self.panel.set_snapshot(self.snapshot) def tearDown(self): self.panel.close() def calculate(self): self.panel.calculate() deadline = time.monotonic() + 5 while self.panel._job is not None and time.monotonic() < deadline: self.app.processEvents() time.sleep(.002) self.assertIsNone(self.panel._job) self.assertIsNotNone(self.panel.curve, self.panel.status.text()) def test_all_methods_are_available_and_parameter_changes_clear_result(self): for method in ("polynomial", "linear", "pchip", "spline"): self.panel.method.setCurrentIndex(self.panel.method.findData(method)) self.calculate() self.assertEqual(method, self.panel.curve.request.method) self.panel.count.setValue(self.panel.count.value() + 1) self.assertIsNone(self.panel.curve) self.assertFalse(self.panel.export_button.isEnabled()) def test_source_units_and_blocking_invalidate_result(self): for changed in (replace(self.snapshot, axis=Axis("Frequency", "Hz")), replace(self.snapshot, source="new file"), replace(self.snapshot, x_range=(0, 1)), replace(self.snapshot, blocked_reason="FFT")): self.panel.set_snapshot(self.snapshot) self.calculate() self.panel.set_snapshot(changed) self.assertIsNone(self.panel.curve) self.assertFalse(self.panel.export_button.isEnabled()) self.assertFalse(self.panel.apply_button.isEnabled()) def test_late_worker_result_is_rejected_even_after_source_returns(self): # Capture, but do not schedule, the real worker. Deliver its answer after # the source changes away and back to the same numerical values. with patch("set_devices.qt_ports.plot_processing.QThreadPool"): self.panel.calculate() job = self.panel._job self.panel.set_snapshot(replace(self.snapshot, source="another")) self.panel.set_snapshot(self.snapshot) self.panel._finished(job.signature, process(job.signature), "") self.assertIsNone(self.panel.curve) self.assertFalse(self.panel.export_button.isEnabled()) def test_attachment_is_lazy_and_coalesces_source_notifications(self): widget = QWidget() calls = [] attachment = PlotProcessingAttachment(widget, lambda: calls.append(1) or self.snapshot, widget.update) try: for _ in range(10): attachment.source_changed() self.app.processEvents() self.assertEqual([], calls) self.assertIsNone(attachment.panel) attachment.open() self.assertEqual([1], calls) for _ in range(10): attachment.source_changed() self.app.processEvents() self.assertEqual([1, 1], calls) finally: widget.close() if __name__ == "__main__": unittest.main()