"""Tests for src/pygui/backend/visualization/wafer_map_item.py.""" from unittest.mock import MagicMock import pytest # Use the session-scoped qapp fixture from conftest.py so Qt Property/Signal # descriptors work properly. pytestmark = pytest.mark.usefixtures("qapp") def test_export_image_empty_path(): """export_image returns False for empty/None path.""" from pygui.backend.visualization.wafer_map_item import WaferMapItem item = WaferMapItem() assert item.export_image("") is False assert item.export_image(None) is False def test_export_image_zero_size_item_fails(): """export_image returns False when the item has no geometry to render.""" from pygui.backend.visualization.wafer_map_item import WaferMapItem item = WaferMapItem() assert item.export_image("/tmp/test_wafer.png") is False def test_export_image_unwritable_path_fails(tmp_path): """export_image returns False when the target directory doesn't exist.""" from pygui.backend.visualization.wafer_map_item import WaferMapItem item = WaferMapItem() item.setWidth(100) item.setHeight(100) item.values = [10.0, 20.0, 30.0] assert item.export_image(str(tmp_path / "no_such_dir" / "wafer.png")) is False def test_thickness_properties(): """Thickness data properties exist with correct defaults.""" from pygui.backend.visualization.wafer_map_item import WaferMapItem item = WaferMapItem() assert item._thickness_data == [] assert item._show_thickness is False def test_thickness_setter_updates_data(): """Setting thicknessData updates _thickness_data.""" from pygui.backend.visualization.wafer_map_item import WaferMapItem item = WaferMapItem() item._rebuild_thickness = MagicMock() item.thicknessData = [1.0, 2.0, 3.0] assert item._thickness_data == [1.0, 2.0, 3.0] assert item._rebuild_thickness.called def test_show_thickness_setter(): """Setting showThickness updates _show_thickness.""" from pygui.backend.visualization.wafer_map_item import WaferMapItem item = WaferMapItem() assert item._show_thickness is False item.showThickness = True assert item._show_thickness is True def test_export_image_writes_real_png_headless(tmp_path): """export_image must produce an actual decodable image file without a QQuickWindow — the button was silently failing because grabToImage() resolves asynchronously and never completes headless/immediately.""" from PySide6.QtGui import QImage from pygui.backend.visualization.wafer_map_item import WaferMapItem item = WaferMapItem() item.setWidth(200) item.setHeight(200) item.values = [10.0, 20.0, 30.0] out = tmp_path / "wafer.png" assert item.export_image(str(out)) is True assert out.exists() loaded = QImage(str(out)) assert not loaded.isNull() assert loaded.width() == 200 def test_export_image_includes_metrics_footer(tmp_path): """With sensor values loaded, the export gains a footer strip below the map carrying the metric readouts (sensors / min / max / avg).""" from PySide6.QtGui import QImage from pygui.backend.visualization.wafer_map_item import WaferMapItem item = WaferMapItem() item.setWidth(200) item.setHeight(200) item.values = [10.0, 20.0, 30.0] out = tmp_path / "wafer.png" assert item.export_image(str(out)) is True loaded = QImage(str(out)) assert loaded.height() > 200 # footer strip appended # footer is painted opaque (dark bg), not transparent assert loaded.pixelColor(5, loaded.height() - 5).alpha() == 255 def test_export_image_extra_line_grows_footer(tmp_path): """Live export passes an extra metrics line; the footer gains a second row for it. Review export (no extra) keeps the single-row footer.""" from PySide6.QtGui import QImage from pygui.backend.visualization.wafer_map_item import WaferMapItem item = WaferMapItem() item.setWidth(200) item.setHeight(200) item.values = [10.0, 20.0, 30.0] review = tmp_path / "review.png" live = tmp_path / "live.png" assert item.export_image(str(review)) is True assert item.export_image(str(live), "Frames: 42 Errors: 0") is True h_review = QImage(str(review)).height() h_live = QImage(str(live)).height() assert h_review > 200 # readout footer present assert h_live > h_review # extra live row appended def test_export_image_highlights_hottest_and_coldest(tmp_path): """The exported map rings the hottest sensor in the high color and the coldest in the low color. With no bands set every marker fill is the in-range green, so any high/low-colored pixels must come from the rings.""" from PySide6.QtGui import QImage from pygui.backend.visualization.wafer_map_item import WaferMapItem item = WaferMapItem() item.setWidth(200) item.setHeight(200) item.sensors = [ {"label": "1", "x": -50.0, "y": 0.0}, {"label": "2", "x": 0.0, "y": 0.0}, {"label": "3", "x": 50.0, "y": 0.0}, ] item.values = [10.0, 20.0, 30.0] out = tmp_path / "wafer.png" assert item.export_image(str(out)) is True img = QImage(str(out)) found = {img.pixelColor(x, y).name() for y in range(img.height()) for x in range(img.width())} assert "#ef4444" in found # hottest ring (sensorHigh) assert "#5b9df5" in found # coldest ring (sensorLow) def test_readout_text_full_stats(): """Export footer readout carries every READOUT stat with sensor indices. Worked example: [10, 20, 30] → min 10 (#1), max 30 (#3), diff 20, avg 20, σ = sqrt(200/3) ≈ 8.16, 3σ ≈ 24.49. """ from pygui.backend.visualization.wafer_map_item import readout_text assert readout_text([10.0, 20.0, 30.0]) == ( "Sensors: 3 Min: 10.00°C (#1) Max: 30.00°C (#3) " "Diff: 20.00 Avg: 20.00 σ: 8.16 3σ: 24.49" ) def test_readout_text_empty(): from pygui.backend.visualization.wafer_map_item import readout_text assert readout_text([]) == "" def test_export_image_no_data_fails(tmp_path): """No file loaded / no stream played -> nothing meaningful to export.""" from pygui.backend.visualization.wafer_map_item import WaferMapItem item = WaferMapItem() item.setWidth(200) item.setHeight(200) out = tmp_path / "wafer.png" assert item.export_image(str(out)) is False assert not out.exists()