mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-03-04 16:02:51 +01:00
fix(heatmap): devices are saved as SafeProperties
This commit is contained in:
@@ -2039,6 +2039,90 @@ class Heatmap(RPCBase):
|
||||
reload (bool): Whether to reload the heatmap with new data.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def x_device_name(self) -> "str":
|
||||
"""
|
||||
Device name for the X axis.
|
||||
"""
|
||||
|
||||
@x_device_name.setter
|
||||
@rpc_call
|
||||
def x_device_name(self) -> "str":
|
||||
"""
|
||||
Device name for the X axis.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def x_device_entry(self) -> "str":
|
||||
"""
|
||||
Signal entry for the X axis device.
|
||||
"""
|
||||
|
||||
@x_device_entry.setter
|
||||
@rpc_call
|
||||
def x_device_entry(self) -> "str":
|
||||
"""
|
||||
Signal entry for the X axis device.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def y_device_name(self) -> "str":
|
||||
"""
|
||||
Device name for the Y axis.
|
||||
"""
|
||||
|
||||
@y_device_name.setter
|
||||
@rpc_call
|
||||
def y_device_name(self) -> "str":
|
||||
"""
|
||||
Device name for the Y axis.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def y_device_entry(self) -> "str":
|
||||
"""
|
||||
Signal entry for the Y axis device.
|
||||
"""
|
||||
|
||||
@y_device_entry.setter
|
||||
@rpc_call
|
||||
def y_device_entry(self) -> "str":
|
||||
"""
|
||||
Signal entry for the Y axis device.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def z_device_name(self) -> "str":
|
||||
"""
|
||||
Device name for the Z (color) axis.
|
||||
"""
|
||||
|
||||
@z_device_name.setter
|
||||
@rpc_call
|
||||
def z_device_name(self) -> "str":
|
||||
"""
|
||||
Device name for the Z (color) axis.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def z_device_entry(self) -> "str":
|
||||
"""
|
||||
Signal entry for the Z (color) axis device.
|
||||
"""
|
||||
|
||||
@z_device_entry.setter
|
||||
@rpc_call
|
||||
def z_device_entry(self) -> "str":
|
||||
"""
|
||||
Signal entry for the Z (color) axis device.
|
||||
"""
|
||||
|
||||
|
||||
class Image(RPCBase):
|
||||
"""Image widget for displaying 2D data."""
|
||||
|
||||
@@ -203,6 +203,19 @@ class Heatmap(ImageBase):
|
||||
"remove_roi",
|
||||
"rois",
|
||||
"plot",
|
||||
# Device properties
|
||||
"x_device_name",
|
||||
"x_device_name.setter",
|
||||
"x_device_entry",
|
||||
"x_device_entry.setter",
|
||||
"y_device_name",
|
||||
"y_device_name.setter",
|
||||
"y_device_entry",
|
||||
"y_device_entry.setter",
|
||||
"z_device_name",
|
||||
"z_device_name.setter",
|
||||
"z_device_entry",
|
||||
"z_device_entry.setter",
|
||||
]
|
||||
|
||||
PLUGIN = True
|
||||
@@ -413,9 +426,15 @@ class Heatmap(ImageBase):
|
||||
"""
|
||||
if self._image_config is None:
|
||||
return
|
||||
x_name = self._image_config.x_device.name
|
||||
y_name = self._image_config.y_device.name
|
||||
z_name = self._image_config.z_device.name
|
||||
|
||||
# Safely get device names (might be None if not yet configured)
|
||||
x_device = self._image_config.x_device
|
||||
y_device = self._image_config.y_device
|
||||
z_device = self._image_config.z_device
|
||||
|
||||
x_name = x_device.name if x_device else None
|
||||
y_name = y_device.name if y_device else None
|
||||
z_name = z_device.name if z_device else None
|
||||
|
||||
if x_name is not None:
|
||||
self.x_label = x_name # type: ignore
|
||||
@@ -1136,6 +1155,244 @@ class Heatmap(ImageBase):
|
||||
self.crosshair.reset()
|
||||
super().reset()
|
||||
|
||||
################################################################################
|
||||
# Widget Specific Properties
|
||||
################################################################################
|
||||
|
||||
@SafeProperty(str)
|
||||
def x_device_name(self) -> str:
|
||||
"""Device name for the X axis."""
|
||||
if self._image_config.x_device is None:
|
||||
return ""
|
||||
return self._image_config.x_device.name or ""
|
||||
|
||||
@x_device_name.setter
|
||||
def x_device_name(self, device_name: str) -> None:
|
||||
"""
|
||||
Set the X device name.
|
||||
|
||||
Args:
|
||||
device_name(str): Device name for the X axis
|
||||
"""
|
||||
device_name = device_name or ""
|
||||
|
||||
# Get current entry or validate
|
||||
if device_name:
|
||||
try:
|
||||
entry = self.entry_validator.validate_signal(device_name, None)
|
||||
self._image_config.x_device = HeatmapDeviceSignal(name=device_name, entry=entry)
|
||||
self.property_changed.emit("x_device_name", device_name)
|
||||
self.update_labels() # Update axis labels
|
||||
self._try_auto_plot()
|
||||
except Exception:
|
||||
pass # Silently fail if device is not available yet
|
||||
else:
|
||||
self._image_config.x_device = None
|
||||
self.property_changed.emit("x_device_name", "")
|
||||
self.update_labels() # Clear axis labels
|
||||
|
||||
@SafeProperty(str)
|
||||
def x_device_entry(self) -> str:
|
||||
"""Signal entry for the X axis device."""
|
||||
if self._image_config.x_device is None:
|
||||
return ""
|
||||
return self._image_config.x_device.entry or ""
|
||||
|
||||
@x_device_entry.setter
|
||||
def x_device_entry(self, entry: str) -> None:
|
||||
"""
|
||||
Set the X device entry.
|
||||
|
||||
Args:
|
||||
entry(str): Signal entry for the X axis device
|
||||
"""
|
||||
if not entry:
|
||||
return
|
||||
|
||||
if self._image_config.x_device is None:
|
||||
logger.warning("Cannot set x_device_entry without x_device_name set first.")
|
||||
return
|
||||
|
||||
device_name = self._image_config.x_device.name
|
||||
try:
|
||||
# Validate the entry for this device
|
||||
validated_entry = self.entry_validator.validate_signal(device_name, entry)
|
||||
self._image_config.x_device = HeatmapDeviceSignal(
|
||||
name=device_name, entry=validated_entry
|
||||
)
|
||||
self.property_changed.emit("x_device_entry", validated_entry)
|
||||
self.update_labels() # Update axis labels
|
||||
self._try_auto_plot()
|
||||
except Exception:
|
||||
pass # Silently fail if validation fails
|
||||
|
||||
@SafeProperty(str)
|
||||
def y_device_name(self) -> str:
|
||||
"""Device name for the Y axis."""
|
||||
if self._image_config.y_device is None:
|
||||
return ""
|
||||
return self._image_config.y_device.name or ""
|
||||
|
||||
@y_device_name.setter
|
||||
def y_device_name(self, device_name: str) -> None:
|
||||
"""
|
||||
Set the Y device name.
|
||||
|
||||
Args:
|
||||
device_name(str): Device name for the Y axis
|
||||
"""
|
||||
device_name = device_name or ""
|
||||
|
||||
# Get current entry or validate
|
||||
if device_name:
|
||||
try:
|
||||
entry = self.entry_validator.validate_signal(device_name, None)
|
||||
self._image_config.y_device = HeatmapDeviceSignal(name=device_name, entry=entry)
|
||||
self.property_changed.emit("y_device_name", device_name)
|
||||
self.update_labels() # Update axis labels
|
||||
self._try_auto_plot()
|
||||
except Exception:
|
||||
pass # Silently fail if device is not available yet
|
||||
else:
|
||||
self._image_config.y_device = None
|
||||
self.property_changed.emit("y_device_name", "")
|
||||
self.update_labels() # Clear axis labels
|
||||
|
||||
@SafeProperty(str)
|
||||
def y_device_entry(self) -> str:
|
||||
"""Signal entry for the Y axis device."""
|
||||
if self._image_config.y_device is None:
|
||||
return ""
|
||||
return self._image_config.y_device.entry or ""
|
||||
|
||||
@y_device_entry.setter
|
||||
def y_device_entry(self, entry: str) -> None:
|
||||
"""
|
||||
Set the Y device entry.
|
||||
|
||||
Args:
|
||||
entry(str): Signal entry for the Y axis device
|
||||
"""
|
||||
if not entry:
|
||||
return
|
||||
|
||||
if self._image_config.y_device is None:
|
||||
logger.warning("Cannot set y_device_entry without y_device_name set first.")
|
||||
return
|
||||
|
||||
device_name = self._image_config.y_device.name
|
||||
try:
|
||||
# Validate the entry for this device
|
||||
validated_entry = self.entry_validator.validate_signal(device_name, entry)
|
||||
self._image_config.y_device = HeatmapDeviceSignal(
|
||||
name=device_name, entry=validated_entry
|
||||
)
|
||||
self.property_changed.emit("y_device_entry", validated_entry)
|
||||
self.update_labels() # Update axis labels
|
||||
self._try_auto_plot()
|
||||
except Exception as e:
|
||||
logger.debug(f"Y device entry validation failed: {e}")
|
||||
pass # Silently fail if validation fails
|
||||
|
||||
@SafeProperty(str)
|
||||
def z_device_name(self) -> str:
|
||||
"""Device name for the Z (color) axis."""
|
||||
if self._image_config.z_device is None:
|
||||
return ""
|
||||
return self._image_config.z_device.name or ""
|
||||
|
||||
@z_device_name.setter
|
||||
def z_device_name(self, device_name: str) -> None:
|
||||
"""
|
||||
Set the Z device name.
|
||||
|
||||
Args:
|
||||
device_name(str): Device name for the Z axis
|
||||
"""
|
||||
device_name = device_name or ""
|
||||
|
||||
# Get current entry or validate
|
||||
if device_name:
|
||||
try:
|
||||
entry = self.entry_validator.validate_signal(device_name, None)
|
||||
self._image_config.z_device = HeatmapDeviceSignal(name=device_name, entry=entry)
|
||||
self.property_changed.emit("z_device_name", device_name)
|
||||
self.update_labels() # Update axis labels (title)
|
||||
self._try_auto_plot()
|
||||
except Exception as e:
|
||||
logger.debug(f"Z device name validation failed: {e}")
|
||||
pass # Silently fail if device is not available yet
|
||||
else:
|
||||
self._image_config.z_device = None
|
||||
self.property_changed.emit("z_device_name", "")
|
||||
self.update_labels() # Clear axis labels
|
||||
|
||||
@SafeProperty(str)
|
||||
def z_device_entry(self) -> str:
|
||||
"""Signal entry for the Z (color) axis device."""
|
||||
if self._image_config.z_device is None:
|
||||
return ""
|
||||
return self._image_config.z_device.entry or ""
|
||||
|
||||
@z_device_entry.setter
|
||||
def z_device_entry(self, entry: str) -> None:
|
||||
"""
|
||||
Set the Z device entry.
|
||||
|
||||
Args:
|
||||
entry(str): Signal entry for the Z axis device
|
||||
"""
|
||||
if not entry:
|
||||
return
|
||||
|
||||
if self._image_config.z_device is None:
|
||||
logger.warning("Cannot set z_device_entry without z_device_name set first.")
|
||||
return
|
||||
|
||||
device_name = self._image_config.z_device.name
|
||||
try:
|
||||
# Validate the entry for this device
|
||||
validated_entry = self.entry_validator.validate_signal(device_name, entry)
|
||||
self._image_config.z_device = HeatmapDeviceSignal(
|
||||
name=device_name, entry=validated_entry
|
||||
)
|
||||
self.property_changed.emit("z_device_entry", validated_entry)
|
||||
self.update_labels() # Update axis labels (title)
|
||||
self._try_auto_plot()
|
||||
except Exception as e:
|
||||
logger.debug(f"Z device entry validation failed: {e}")
|
||||
pass # Silently fail if validation fails
|
||||
|
||||
def _try_auto_plot(self) -> None:
|
||||
"""
|
||||
Attempt to automatically call plot() if all three devices are set.
|
||||
Similar to waveform's approach but requires all three devices.
|
||||
"""
|
||||
has_x = self._image_config.x_device is not None
|
||||
has_y = self._image_config.y_device is not None
|
||||
has_z = self._image_config.z_device is not None
|
||||
|
||||
if has_x and has_y and has_z:
|
||||
x_name = self._image_config.x_device.name
|
||||
x_entry = self._image_config.x_device.entry
|
||||
y_name = self._image_config.y_device.name
|
||||
y_entry = self._image_config.y_device.entry
|
||||
z_name = self._image_config.z_device.name
|
||||
z_entry = self._image_config.z_device.entry
|
||||
try:
|
||||
self.plot(
|
||||
x_name=x_name,
|
||||
y_name=y_name,
|
||||
z_name=z_name,
|
||||
x_entry=x_entry,
|
||||
y_entry=y_entry,
|
||||
z_entry=z_entry,
|
||||
validate_bec=False, # Don't validate - entries already validated
|
||||
)
|
||||
except Exception as e:
|
||||
logger.debug(f"Auto-plot failed: {e}")
|
||||
pass # Silently fail if plot cannot be called yet
|
||||
|
||||
@SafeProperty(str)
|
||||
def interpolation_method(self) -> str:
|
||||
"""
|
||||
|
||||
@@ -597,3 +597,277 @@ def test_finish_interpolation_thread_cleans_references(heatmap_widget):
|
||||
thread_mock.deleteLater.assert_called_once()
|
||||
assert heatmap_widget._interpolation_worker is None
|
||||
assert heatmap_widget._interpolation_thread is None
|
||||
|
||||
|
||||
def test_device_safe_properties_get(heatmap_widget):
|
||||
"""Test that device SafeProperty getters work correctly."""
|
||||
# Initially devices should be empty
|
||||
assert heatmap_widget.x_device_name == ""
|
||||
assert heatmap_widget.x_device_entry == ""
|
||||
assert heatmap_widget.y_device_name == ""
|
||||
assert heatmap_widget.y_device_entry == ""
|
||||
assert heatmap_widget.z_device_name == ""
|
||||
assert heatmap_widget.z_device_entry == ""
|
||||
|
||||
# Set devices via plot
|
||||
heatmap_widget.plot(x_name="samx", y_name="samy", z_name="bpm4i")
|
||||
|
||||
# Check properties return device names and entries separately
|
||||
assert heatmap_widget.x_device_name == "samx"
|
||||
assert heatmap_widget.x_device_entry # Should have some entry
|
||||
assert heatmap_widget.y_device_name == "samy"
|
||||
assert heatmap_widget.y_device_entry # Should have some entry
|
||||
assert heatmap_widget.z_device_name == "bpm4i"
|
||||
assert heatmap_widget.z_device_entry # Should have some entry
|
||||
|
||||
|
||||
def test_device_safe_properties_set_name(heatmap_widget):
|
||||
"""Test that device SafeProperty setters work for device names."""
|
||||
# Set x_device_name - should auto-validate entry
|
||||
heatmap_widget.x_device_name = "samx"
|
||||
assert heatmap_widget._image_config.x_device is not None
|
||||
assert heatmap_widget._image_config.x_device.name == "samx"
|
||||
assert heatmap_widget._image_config.x_device.entry is not None # Entry should be validated
|
||||
assert heatmap_widget.x_device_name == "samx"
|
||||
|
||||
# Set y_device_name
|
||||
heatmap_widget.y_device_name = "samy"
|
||||
assert heatmap_widget._image_config.y_device is not None
|
||||
assert heatmap_widget._image_config.y_device.name == "samy"
|
||||
assert heatmap_widget._image_config.y_device.entry is not None
|
||||
assert heatmap_widget.y_device_name == "samy"
|
||||
|
||||
# Set z_device_name
|
||||
heatmap_widget.z_device_name = "bpm4i"
|
||||
assert heatmap_widget._image_config.z_device is not None
|
||||
assert heatmap_widget._image_config.z_device.name == "bpm4i"
|
||||
assert heatmap_widget._image_config.z_device.entry is not None
|
||||
assert heatmap_widget.z_device_name == "bpm4i"
|
||||
|
||||
|
||||
def test_device_safe_properties_set_entry(heatmap_widget):
|
||||
"""Test that device entry properties can override default entries."""
|
||||
# Set device name first - this auto-validates entry
|
||||
heatmap_widget.x_device_name = "samx"
|
||||
initial_entry = heatmap_widget.x_device_entry
|
||||
assert initial_entry # Should have auto-validated entry
|
||||
|
||||
# Override with specific entry
|
||||
heatmap_widget.x_device_entry = "samx"
|
||||
assert heatmap_widget._image_config.x_device.entry == "samx"
|
||||
assert heatmap_widget.x_device_entry == "samx"
|
||||
|
||||
# Same for y device
|
||||
heatmap_widget.y_device_name = "samy"
|
||||
heatmap_widget.y_device_entry = "samy_setpoint"
|
||||
assert heatmap_widget._image_config.y_device.entry == "samy_setpoint"
|
||||
|
||||
# Same for z device
|
||||
heatmap_widget.z_device_name = "bpm4i"
|
||||
heatmap_widget.z_device_entry = "bpm4i"
|
||||
assert heatmap_widget._image_config.z_device.entry == "bpm4i"
|
||||
|
||||
|
||||
def test_device_entry_cannot_be_set_without_name(heatmap_widget):
|
||||
"""Test that setting entry without device name logs warning and does nothing."""
|
||||
# Try to set entry without device name
|
||||
heatmap_widget.x_device_entry = "some_entry"
|
||||
# Should not crash, entry should remain empty
|
||||
assert heatmap_widget.x_device_entry == ""
|
||||
assert heatmap_widget._image_config.x_device is None
|
||||
|
||||
|
||||
def test_device_safe_properties_set_empty(heatmap_widget):
|
||||
"""Test that device SafeProperty setters handle empty strings."""
|
||||
# Set device first
|
||||
heatmap_widget.x_device_name = "samx"
|
||||
assert heatmap_widget._image_config.x_device is not None
|
||||
|
||||
# Set to empty string - should clear the device
|
||||
heatmap_widget.x_device_name = ""
|
||||
assert heatmap_widget.x_device_name == ""
|
||||
assert heatmap_widget._image_config.x_device is None
|
||||
|
||||
|
||||
def test_device_safe_properties_auto_plot(heatmap_widget):
|
||||
"""Test that setting all three devices triggers auto-plot."""
|
||||
# Set all three devices
|
||||
heatmap_widget.x_device_name = "samx"
|
||||
heatmap_widget.y_device_name = "samy"
|
||||
heatmap_widget.z_device_name = "bpm4i"
|
||||
|
||||
# Check that plot was called (image_config should be updated)
|
||||
assert heatmap_widget._image_config.x_device is not None
|
||||
assert heatmap_widget._image_config.y_device is not None
|
||||
assert heatmap_widget._image_config.z_device is not None
|
||||
|
||||
|
||||
def test_device_properties_update_labels(heatmap_widget):
|
||||
"""Test that setting device properties updates axis labels."""
|
||||
# Set x device - should update x label
|
||||
heatmap_widget.x_device_name = "samx"
|
||||
assert heatmap_widget.x_label == "samx"
|
||||
|
||||
# Set y device - should update y label
|
||||
heatmap_widget.y_device_name = "samy"
|
||||
assert heatmap_widget.y_label == "samy"
|
||||
|
||||
# Set z device - should update title
|
||||
heatmap_widget.z_device_name = "bpm4i"
|
||||
assert heatmap_widget.title == "bpm4i"
|
||||
|
||||
|
||||
def test_device_properties_partial_configuration(heatmap_widget):
|
||||
"""Test that widget handles partial device configuration gracefully."""
|
||||
# Set only x device
|
||||
heatmap_widget.x_device_name = "samx"
|
||||
assert heatmap_widget.x_device_name == "samx"
|
||||
assert heatmap_widget.y_device_name == ""
|
||||
assert heatmap_widget.z_device_name == ""
|
||||
|
||||
# Set only y device (x already set)
|
||||
heatmap_widget.y_device_name = "samy"
|
||||
assert heatmap_widget.x_device_name == "samx"
|
||||
assert heatmap_widget.y_device_name == "samy"
|
||||
assert heatmap_widget.z_device_name == ""
|
||||
|
||||
# Auto-plot should not trigger yet (z missing)
|
||||
# But devices should be configured
|
||||
assert heatmap_widget._image_config.x_device is not None
|
||||
assert heatmap_widget._image_config.y_device is not None
|
||||
|
||||
|
||||
def test_device_properties_in_user_access(heatmap_widget):
|
||||
"""Test that device properties are exposed in USER_ACCESS for RPC."""
|
||||
from bec_widgets.widgets.plots.heatmap.heatmap import Heatmap
|
||||
|
||||
assert "x_device_name" in Heatmap.USER_ACCESS
|
||||
assert "x_device_name.setter" in Heatmap.USER_ACCESS
|
||||
assert "x_device_entry" in Heatmap.USER_ACCESS
|
||||
assert "x_device_entry.setter" in Heatmap.USER_ACCESS
|
||||
assert "y_device_name" in Heatmap.USER_ACCESS
|
||||
assert "y_device_name.setter" in Heatmap.USER_ACCESS
|
||||
assert "y_device_entry" in Heatmap.USER_ACCESS
|
||||
assert "y_device_entry.setter" in Heatmap.USER_ACCESS
|
||||
assert "z_device_name" in Heatmap.USER_ACCESS
|
||||
assert "z_device_name.setter" in Heatmap.USER_ACCESS
|
||||
assert "z_device_entry" in Heatmap.USER_ACCESS
|
||||
assert "z_device_entry.setter" in Heatmap.USER_ACCESS
|
||||
|
||||
|
||||
def test_device_properties_validation(heatmap_widget):
|
||||
"""Test that device entries are validated through entry_validator."""
|
||||
# Set device name - entry should be auto-validated
|
||||
heatmap_widget.x_device_name = "samx"
|
||||
initial_entry = heatmap_widget.x_device_entry
|
||||
|
||||
# The entry should be validated (will be "samx" in the mock)
|
||||
assert initial_entry == "samx"
|
||||
|
||||
# Set a different entry - should also be validated
|
||||
heatmap_widget.x_device_entry = "samx" # Use same name as validated entry
|
||||
assert heatmap_widget.x_device_entry == "samx"
|
||||
|
||||
|
||||
def test_device_properties_with_plot_method(heatmap_widget):
|
||||
"""Test that device properties reflect values set via plot() method."""
|
||||
# Use plot method
|
||||
heatmap_widget.plot(x_name="samx", y_name="samy", z_name="bpm4i")
|
||||
|
||||
# Properties should reflect the plotted devices
|
||||
assert heatmap_widget.x_device_name == "samx"
|
||||
assert heatmap_widget.y_device_name == "samy"
|
||||
assert heatmap_widget.z_device_name == "bpm4i"
|
||||
|
||||
# Entries should be validated
|
||||
assert heatmap_widget.x_device_entry == "samx"
|
||||
assert heatmap_widget.y_device_entry == "samy"
|
||||
assert heatmap_widget.z_device_entry == "bpm4i"
|
||||
|
||||
|
||||
def test_device_properties_overwrite_via_properties(heatmap_widget):
|
||||
"""Test that device properties can overwrite values set via plot()."""
|
||||
# First set via plot
|
||||
heatmap_widget.plot(x_name="samx", y_name="samy", z_name="bpm4i")
|
||||
|
||||
# Overwrite x device via properties
|
||||
heatmap_widget.x_device_name = "samz"
|
||||
assert heatmap_widget.x_device_name == "samz"
|
||||
assert heatmap_widget._image_config.x_device.name == "samz"
|
||||
|
||||
# Overwrite y device entry
|
||||
heatmap_widget.y_device_entry = "samy"
|
||||
assert heatmap_widget.y_device_entry == "samy"
|
||||
|
||||
|
||||
def test_device_properties_clearing_devices(heatmap_widget):
|
||||
"""Test clearing devices by setting to empty string."""
|
||||
# Set all devices
|
||||
heatmap_widget.x_device_name = "samx"
|
||||
heatmap_widget.y_device_name = "samy"
|
||||
heatmap_widget.z_device_name = "bpm4i"
|
||||
|
||||
# Clear x device
|
||||
heatmap_widget.x_device_name = ""
|
||||
assert heatmap_widget.x_device_name == ""
|
||||
assert heatmap_widget._image_config.x_device is None
|
||||
|
||||
# Y and Z should still be set
|
||||
assert heatmap_widget.y_device_name == "samy"
|
||||
assert heatmap_widget.z_device_name == "bpm4i"
|
||||
|
||||
|
||||
def test_device_properties_property_changed_signal(heatmap_widget):
|
||||
"""Test that property_changed signal is emitted when devices are set."""
|
||||
from unittest.mock import Mock
|
||||
|
||||
# Connect mock to property_changed signal
|
||||
mock_handler = Mock()
|
||||
heatmap_widget.property_changed.connect(mock_handler)
|
||||
|
||||
# Set device name
|
||||
heatmap_widget.x_device_name = "samx"
|
||||
|
||||
# Signal should have been emitted
|
||||
assert mock_handler.called
|
||||
# Check it was called with correct arguments
|
||||
mock_handler.assert_any_call("x_device_name", "samx")
|
||||
|
||||
|
||||
def test_device_entry_validation_with_invalid_device(heatmap_widget):
|
||||
"""Test that invalid device names are handled gracefully."""
|
||||
# Try to set invalid device name
|
||||
heatmap_widget.x_device_name = "nonexistent_device"
|
||||
|
||||
# Should not crash, but device might not be set if validation fails
|
||||
# The implementation silently fails, so we just check it doesn't crash
|
||||
|
||||
|
||||
def test_device_properties_sequential_entry_changes(heatmap_widget):
|
||||
"""Test changing device entry multiple times."""
|
||||
# Set device
|
||||
heatmap_widget.x_device_name = "samx"
|
||||
|
||||
# Change entry multiple times
|
||||
heatmap_widget.x_device_entry = "samx_velocity"
|
||||
assert heatmap_widget.x_device_entry == "samx_velocity"
|
||||
|
||||
heatmap_widget.x_device_entry = "samx_setpoint"
|
||||
assert heatmap_widget.x_device_entry == "samx_setpoint"
|
||||
|
||||
heatmap_widget.x_device_entry = "samx"
|
||||
assert heatmap_widget.x_device_entry == "samx"
|
||||
|
||||
|
||||
def test_device_properties_with_none_values(heatmap_widget):
|
||||
"""Test that None values are handled as empty strings."""
|
||||
# Device name None should be treated as empty
|
||||
heatmap_widget.x_device_name = None
|
||||
assert heatmap_widget.x_device_name == ""
|
||||
|
||||
# Set a device first
|
||||
heatmap_widget.y_device_name = "samy"
|
||||
|
||||
# Entry None should not change anything
|
||||
heatmap_widget.y_device_entry = None
|
||||
assert heatmap_widget.y_device_entry # Should still have validated entry
|
||||
|
||||
Reference in New Issue
Block a user