1
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2026-03-13 04:08:01 +01:00

refactor: global refactoring to use device-signal pair names

This commit is contained in:
2026-02-02 15:29:32 +01:00
parent c1d4758e4c
commit b93fbc5cd3
38 changed files with 1367 additions and 1388 deletions

View File

@@ -58,8 +58,8 @@ def test_rpc_add_dock_with_plots_e2e(qtbot, bec_client_lib, connected_client_gui
assert gui._ipython_registry[mm._gui_id].__class__ == MotorMap
mm.map("samx", "samy")
curve = wf.plot(x_name="samx", y_name="bpm4i")
im_item = im.image(device_name="eiger", device_entry="preview")
curve = wf.plot(device_x="samx", device_y="bpm4i")
im_item = im.image(device="eiger", signal="preview")
assert curve.__class__.__name__ == "RPCReference"
assert curve.__class__ == RPCReference

View File

@@ -34,7 +34,7 @@ def test_rpc_plotting_shortcuts_init_configs(qtbot, connected_client_gui_obj):
sw = dock_area.new("ScatterWaveform")
mw = dock_area.new("MultiWaveform")
c1 = wf.plot(x_name="samx", y_name="bpm4i")
c1 = wf.plot(device_x="samx", device_y="bpm4i")
# Adding custom curves, removing one and adding it again should not crash
c2 = wf.plot(y=[1, 2, 3], x=[1, 2, 3])
assert c2.object_name == "Curve_0"
@@ -42,9 +42,9 @@ def test_rpc_plotting_shortcuts_init_configs(qtbot, connected_client_gui_obj):
c3 = wf.plot(y=[1, 2, 3], x=[1, 2, 3])
assert c3.object_name == "Curve_0"
im.image(device_name="eiger", device_entry="preview")
mm.map(x_name="samx", y_name="samy")
sw.plot(x_name="samx", y_name="samy", z_name="bpm4a")
im.image(device="eiger", signal="preview")
mm.map(device_x="samx", device_y="samy")
sw.plot(device_x="samx", device_y="samy", device_z="bpm4a")
mw.plot(monitor="waveform")
# Adding multiple custom curves sho
@@ -70,8 +70,8 @@ def test_rpc_plotting_shortcuts_init_configs(qtbot, connected_client_gui_obj):
# Curve
assert c1._config_dict["signal"] == {
"dap": None,
"name": "bpm4i",
"entry": "bpm4i",
"device": "bpm4i",
"signal": "bpm4i",
"dap_oversample": 1,
}
assert c1._config_dict["source"] == "device"
@@ -90,9 +90,9 @@ def test_rpc_waveform_scan(qtbot, bec_client_lib, connected_client_gui_obj):
wf = dock_area.new("Waveform")
# add 3 different curves to track
wf.plot(x_name="samx", y_name="bpm4i")
wf.plot(x_name="samx", y_name="bpm3a")
wf.plot(x_name="samx", y_name="bpm4d")
wf.plot(device_x="samx", device_y="bpm4i")
wf.plot(device_x="samx", device_y="bpm3a")
wf.plot(device_x="samx", device_y="bpm4d")
status = scans.line_scan(dev.samx, -5, 5, steps=10, exp_time=0.05, relative=False)
status.wait()
@@ -133,7 +133,7 @@ def test_async_plotting(qtbot, bec_client_lib, connected_client_gui_obj):
dev.waveform.async_update.set("add").wait()
dev.waveform.waveform_shape.set(10000).wait()
wf = dock_area.new("Waveform")
curve = wf.plot(y_name="waveform")
curve = wf.plot(device_y="waveform")
status = scans.line_scan(dev.samx, -5, 5, steps=5, exp_time=0.05, relative=False)
status.wait()
@@ -165,7 +165,7 @@ def test_rpc_image(qtbot, bec_client_lib, connected_client_gui_obj):
scans = client.scans
im = dock_area.new("Image")
im.image(device_name="eiger", device_entry="preview")
im.image(device="eiger", signal="preview")
status = scans.line_scan(dev.samx, -5, 5, steps=10, exp_time=0.05, relative=False)
status.wait()
@@ -188,7 +188,7 @@ def test_rpc_motor_map(qtbot, bec_client_lib, connected_client_gui_obj):
dock_area = gui.bec
motor_map = dock_area.new("MotorMap")
motor_map.map(x_name="samx", y_name="samy")
motor_map.map(device_x="samx", device_y="samy")
initial_pos_x = dev.samx.read()["samx"]["value"]
initial_pos_y = dev.samy.read()["samy"]["value"]
@@ -219,7 +219,7 @@ def test_dap_rpc(qtbot, bec_client_lib, connected_client_gui_obj):
dock_area = gui.bec
wf = dock_area.new("Waveform")
wf.plot(x_name="samx", y_name="bpm4i", dap="GaussianModel")
wf.plot(device_x="samx", device_y="bpm4i", dap="GaussianModel")
dev.bpm4i.sim.select_model("GaussianModel")
params = dev.bpm4i.sim.params
@@ -262,7 +262,7 @@ def test_waveform_passing_device(qtbot, bec_client_lib, connected_client_gui_obj
wf = dock_area.new("Waveform")
c1 = wf.plot(
y_name=dev.samx, y_entry=dev.samx.setpoint
device_y=dev.samx, signal_y=dev.samx.setpoint
) # using setpoint to not use readback signal
assert c1.object_name == "samx_samx_setpoint"
@@ -342,7 +342,7 @@ def test_rpc_waveform_history_curve(
# Add curve from history using the chosen selector; single curve per scan to avoid duplicates
kwargs = {history_selector: sel_value}
curve = wf.plot(x_name="samx", y_name="bpm4i", **kwargs)
curve = wf.plot(device_x="samx", device_y="bpm4i", **kwargs)
num_elements = 10

View File

@@ -12,19 +12,19 @@ def test_rpc_reference_objects(connected_client_gui_obj):
dock_area = gui.window_list[0]
plt = dock_area.new("Waveform", object_name="fig")
plt.plot(x_name="samx", y_name="bpm4i")
plt.plot(device_x="samx", device_y="bpm4i")
im = dock_area.new("Image")
im.image(device_name="eiger", device_entry="preview")
im.image(device="eiger", signal="preview")
motor_map = dock_area.new("MotorMap")
motor_map.map("samx", "samy")
plt_z = dock_area.new("Waveform")
plt_z.plot(x_name="samx", y_name="samy", z_name="bpm4i")
plt_z.plot(device_x="samx", device_y="samy", device_z="bpm4i")
assert len(plt_z.curves) == 1
assert len(plt.curves) == 1
assert im.device_name == "eiger"
assert im.device_entry == "preview"
assert im.device == "eiger"
assert im.signal == "preview"
assert isinstance(im.main_image, RPCReference)
image_item = gui._ipython_registry.get(im.main_image._gui_id, None)

View File

@@ -234,7 +234,7 @@ def test_widgets_e2e_image(qtbot, connected_client_gui_obj, random_generator_fro
scans = bec.scans
dev = bec.device_manager.devices
# Test rpc calls
img = widget.image(device_name=dev.eiger.name, device_entry="preview")
img = widget.image(device=dev.eiger.name, signal="preview")
assert img.get_data() is None
# Run a scan and plot the image
s = scans.line_scan(dev.samx, -3, 3, steps=50, exp_time=0.01, relative=False)
@@ -254,7 +254,7 @@ def test_widgets_e2e_image(qtbot, connected_client_gui_obj, random_generator_fro
assert np.allclose(img.get_data(), last_img)
# Now add a device with a preview signal
img = widget.image(device_name="eiger", device_entry="preview")
img = widget.image(device="eiger", signal="preview")
s = scans.line_scan(dev.samx, -3, 3, steps=50, exp_time=0.01, relative=False)
s.wait()

View File

@@ -179,15 +179,15 @@ def test_add_new_curve(curve_tree_fixture):
assert curve_tree.tree.topLevelItemCount() == 0
with patch.object(curve_tree, "_ensure_color_buffer_size") as ensure_spy:
new_item = curve_tree.add_new_curve(name="bpm4i", entry="bpm4i")
new_item = curve_tree.add_new_curve(device="bpm4i", signal="bpm4i")
ensure_spy.assert_called_once()
assert curve_tree.tree.topLevelItemCount() == 1
last_item = curve_tree.all_items[-1]
assert last_item is new_item
assert new_item.config.source == "device"
assert new_item.config.signal.name == "bpm4i"
assert new_item.config.signal.entry == "bpm4i"
assert new_item.config.signal.device == "bpm4i"
assert new_item.config.signal.signal == "bpm4i"
assert new_item.config.color in curve_tree.color_buffer
@@ -197,8 +197,8 @@ def test_renormalize_colors(curve_tree_fixture):
"""
curve_tree, wf = curve_tree_fixture
# Add multiple curves
c1 = curve_tree.add_new_curve(name="bpm4i", entry="bpm4i")
c2 = curve_tree.add_new_curve(name="bpm3a", entry="bpm3a")
c1 = curve_tree.add_new_curve(device="bpm4i", signal="bpm4i")
c2 = curve_tree.add_new_curve(device="bpm3a", signal="bpm3a")
curve_tree.color_buffer = []
set_color_spy_c1 = patch.object(c1.color_button, "set_color")
@@ -215,7 +215,7 @@ def test_expand_collapse(curve_tree_fixture):
Test expand_all_daps() and collapse_all_daps() calls expand/collapse on every top-level item.
"""
curve_tree, wf = curve_tree_fixture
c1 = curve_tree.add_new_curve(name="bpm4i", entry="bpm4i")
c1 = curve_tree.add_new_curve(device="bpm4i", signal="bpm4i")
curve_tree.tree.expandAll()
expand_spy = patch.object(curve_tree.tree, "expandItem")
collapse_spy = patch.object(curve_tree.tree, "collapseItem")
@@ -236,8 +236,8 @@ def test_send_curve_json(curve_tree_fixture, monkeypatch):
"""
curve_tree, wf = curve_tree_fixture
# Add multiple curves
curve_tree.add_new_curve(name="bpm4i", entry="bpm4i")
curve_tree.add_new_curve(name="bpm3a", entry="bpm3a")
curve_tree.add_new_curve(device="bpm4i", signal="bpm4i")
curve_tree.add_new_curve(device="bpm3a", signal="bpm3a")
curve_tree.color_palette = "viridis"
curve_tree.send_curve_json()
@@ -282,7 +282,7 @@ def test_add_dap_row(curve_tree_fixture):
curve_tree, wf = curve_tree_fixture
# Add a device curve first
device_row = curve_tree.add_new_curve(name="bpm4i", entry="bpm4i")
device_row = curve_tree.add_new_curve(device="bpm4i", signal="bpm4i")
assert device_row.source == "device"
assert curve_tree.tree.topLevelItemCount() == 1
assert device_row.childCount() == 0
@@ -299,8 +299,8 @@ def test_add_dap_row(curve_tree_fixture):
assert dap_child.config.parent_label == device_row.config.label
# Check that the DAP inherits device name/entry from parent
assert dap_child.config.signal.name == "bpm4i"
assert dap_child.config.signal.entry == "bpm4i"
assert dap_child.config.signal.device == "bpm4i"
assert dap_child.config.signal.signal == "bpm4i"
# Check that the item is in the curve_tree's all_items list
assert dap_child in curve_tree.all_items
@@ -313,8 +313,8 @@ def test_remove_self_top_level(curve_tree_fixture):
curve_tree, wf = curve_tree_fixture
# Add two device curves
row1 = curve_tree.add_new_curve(name="bpm4i", entry="bpm4i")
row2 = curve_tree.add_new_curve(name="bpm3a", entry="bpm3a")
row1 = curve_tree.add_new_curve(device="bpm4i", signal="bpm4i")
row2 = curve_tree.add_new_curve(device="bpm3a", signal="bpm3a")
assert curve_tree.tree.topLevelItemCount() == 2
assert len(curve_tree.all_items) == 2
@@ -335,7 +335,7 @@ def test_remove_self_child(curve_tree_fixture):
curve_tree, wf = curve_tree_fixture
# Add a device curve and a DAP child
device_row = curve_tree.add_new_curve(name="bpm4i", entry="bpm4i")
device_row = curve_tree.add_new_curve(device="bpm4i", signal="bpm4i")
device_row.add_dap_row()
dap_child = device_row.child(0)
@@ -360,7 +360,7 @@ def test_export_data_dap(curve_tree_fixture):
curve_tree, wf = curve_tree_fixture
# Add a device curve with specific parameters
device_row = curve_tree.add_new_curve(name="bpm4i", entry="bpm4i")
device_row = curve_tree.add_new_curve(device="bpm4i", signal="bpm4i")
# Add a DAP child
device_row.add_dap_row()
@@ -375,8 +375,8 @@ def test_export_data_dap(curve_tree_fixture):
# Check the exported data
assert exported["source"] == "dap"
assert exported["parent_label"] == "bpm4i-bpm4i"
assert exported["signal"]["name"] == "bpm4i"
assert exported["signal"]["entry"] == "bpm4i"
assert exported["signal"]["device"] == "bpm4i"
assert exported["signal"]["signal"] == "bpm4i"
assert exported["signal"]["dap"] == "GaussianModel"
assert exported["label"] == "bpm4i-bpm4i-GaussianModel"
@@ -422,7 +422,7 @@ def test_export_data_history_curve(curve_tree_fixture, scan_history_factory):
wf.client.queue.scan_storage.current_scan = None
# Create a device row and select scan index "2"
device_row = curve_tree.add_new_curve(name="bpm4i", entry="bpm4i")
device_row = curve_tree.add_new_curve(device="bpm4i", signal="bpm4i")
device_row.scan_index_combo.setCurrentText("2")
exported = device_row.export_data()

View File

@@ -30,11 +30,11 @@ def heatmap_widget(qtbot, mocked_client):
def test_heatmap_plot(heatmap_widget):
heatmap_widget.plot(x_name="samx", y_name="samy", z_name="bpm4i")
heatmap_widget.plot(device_x="samx", device_y="samy", device_z="bpm4i")
assert heatmap_widget._image_config.x_device.name == "samx"
assert heatmap_widget._image_config.y_device.name == "samy"
assert heatmap_widget._image_config.z_device.name == "bpm4i"
assert heatmap_widget._image_config.device_x.device == "samx"
assert heatmap_widget._image_config.device_y.device == "samy"
assert heatmap_widget._image_config.device_z.device == "bpm4i"
def test_heatmap_on_scan_status_no_scan_id(heatmap_widget):
@@ -78,7 +78,7 @@ def test_heatmap_get_image_data_grid_scan(heatmap_widget):
info={},
request_inputs={"arg_bundle": ["samx", -5, 5, 10, "samy", -5, 5, 10], "kwargs": {}},
)
heatmap_widget.plot(x_name="samx", y_name="samy", z_name="bpm4i")
heatmap_widget.plot(device_x="samx", device_y="samy", device_z="bpm4i")
heatmap_widget.status_message = scan_msg
with mock.patch.object(heatmap_widget, "get_grid_scan_image") as mock_get_grid_scan_image:
@@ -147,9 +147,9 @@ def test_heatmap_get_grid_scan_image(heatmap_widget):
)
heatmap_widget._image_config = HeatmapConfig(
parent_id="parent_id",
x_device=HeatmapDeviceSignal(name="samx", entry="samx"),
y_device=HeatmapDeviceSignal(name="samy", entry="samy"),
z_device=HeatmapDeviceSignal(name="bpm4i", entry="bpm4i"),
device_x=HeatmapDeviceSignal(device="samx", signal="samx"),
device_y=HeatmapDeviceSignal(device="samy", signal="samy"),
device_z=HeatmapDeviceSignal(device="bpm4i", signal="bpm4i"),
color_map="viridis",
)
img, _ = heatmap_widget.get_grid_scan_image(list(range(100)), msg=scan_msg)
@@ -174,9 +174,9 @@ def _grid_positions(
def test_heatmap_grid_scan_direction_and_snaking_x_fast(heatmap_widget):
heatmap_widget._image_config = HeatmapConfig(
parent_id="parent_id",
x_device=HeatmapDeviceSignal(name="samx", entry="samx"),
y_device=HeatmapDeviceSignal(name="samy", entry="samy"),
z_device=HeatmapDeviceSignal(name="bpm4i", entry="bpm4i"),
device_x=HeatmapDeviceSignal(device="samx", signal="samx"),
device_y=HeatmapDeviceSignal(device="samy", signal="samy"),
device_z=HeatmapDeviceSignal(device="bpm4i", signal="bpm4i"),
color_map="viridis",
)
@@ -219,9 +219,9 @@ def test_heatmap_grid_scan_direction_and_snaking_x_fast(heatmap_widget):
def test_heatmap_grid_scan_direction_and_snaking_y_fast(heatmap_widget):
heatmap_widget._image_config = HeatmapConfig(
parent_id="parent_id",
x_device=HeatmapDeviceSignal(name="samx", entry="samx"),
y_device=HeatmapDeviceSignal(name="samy", entry="samy"),
z_device=HeatmapDeviceSignal(name="bpm4i", entry="bpm4i"),
device_x=HeatmapDeviceSignal(device="samx", signal="samx"),
device_y=HeatmapDeviceSignal(device="samy", signal="samy"),
device_z=HeatmapDeviceSignal(device="bpm4i", signal="bpm4i"),
color_map="viridis",
)
@@ -277,13 +277,13 @@ def test_heatmap_get_step_scan_image(heatmap_widget):
heatmap_widget.scan_item.status_message = scan_msg
heatmap_widget._image_config = HeatmapConfig(
parent_id="parent_id",
x_device=HeatmapDeviceSignal(name="samx", entry="samx"),
y_device=HeatmapDeviceSignal(name="samy", entry="samy"),
z_device=HeatmapDeviceSignal(name="bpm4i", entry="bpm4i"),
device_x=HeatmapDeviceSignal(device="samx", signal="samx"),
device_y=HeatmapDeviceSignal(device="samy", signal="samy"),
device_z=HeatmapDeviceSignal(device="bpm4i", signal="bpm4i"),
color_map="viridis",
)
img, _ = heatmap_widget.get_step_scan_image(
list(np.random.rand(100)), list(np.random.rand(100)), list(range(100)), msg=scan_msg
list(np.random.rand(100)), list(np.random.rand(100)), list(range(100))
)
assert img.shape > (10, 10)
@@ -291,9 +291,9 @@ def test_heatmap_get_step_scan_image(heatmap_widget):
def test_heatmap_update_plot_no_scan_item(heatmap_widget):
heatmap_widget._image_config = HeatmapConfig(
parent_id="parent_id",
x_device=HeatmapDeviceSignal(name="samx", entry="samx"),
y_device=HeatmapDeviceSignal(name="samy", entry="samy"),
z_device=HeatmapDeviceSignal(name="bpm4i", entry="bpm4i"),
device_x=HeatmapDeviceSignal(device="samx", signal="samx"),
device_y=HeatmapDeviceSignal(device="samy", signal="samy"),
device_z=HeatmapDeviceSignal(device="bpm4i", signal="bpm4i"),
color_map="viridis",
)
with mock.patch.object(heatmap_widget.main_image, "setImage") as mock_set_image:
@@ -304,9 +304,9 @@ def test_heatmap_update_plot_no_scan_item(heatmap_widget):
def test_heatmap_update_plot(heatmap_widget):
heatmap_widget._image_config = HeatmapConfig(
parent_id="parent_id",
x_device=HeatmapDeviceSignal(name="samx", entry="samx"),
y_device=HeatmapDeviceSignal(name="samy", entry="samy"),
z_device=HeatmapDeviceSignal(name="bpm4i", entry="bpm4i"),
device_x=HeatmapDeviceSignal(device="samx", signal="samx"),
device_y=HeatmapDeviceSignal(device="samy", signal="samy"),
device_z=HeatmapDeviceSignal(device="bpm4i", signal="bpm4i"),
color_map="viridis",
)
heatmap_widget.scan_item = create_dummy_scan_item()
@@ -331,9 +331,9 @@ def test_heatmap_update_plot(heatmap_widget):
def test_heatmap_update_plot_without_status_message(heatmap_widget):
heatmap_widget._image_config = HeatmapConfig(
parent_id="parent_id",
x_device=HeatmapDeviceSignal(name="samx", entry="samx"),
y_device=HeatmapDeviceSignal(name="samy", entry="samy"),
z_device=HeatmapDeviceSignal(name="bpm4i", entry="bpm4i"),
device_x=HeatmapDeviceSignal(device="samx", signal="samx"),
device_y=HeatmapDeviceSignal(device="samy", signal="samy"),
device_z=HeatmapDeviceSignal(device="bpm4i", signal="bpm4i"),
color_map="viridis",
)
heatmap_widget.scan_item = create_dummy_scan_item()
@@ -346,9 +346,9 @@ def test_heatmap_update_plot_without_status_message(heatmap_widget):
def test_heatmap_update_plot_no_img_data(heatmap_widget):
heatmap_widget._image_config = HeatmapConfig(
parent_id="parent_id",
x_device=HeatmapDeviceSignal(name="samx", entry="samx"),
y_device=HeatmapDeviceSignal(name="samy", entry="samy"),
z_device=HeatmapDeviceSignal(name="bpm4i", entry="bpm4i"),
device_x=HeatmapDeviceSignal(device="samx", signal="samx"),
device_y=HeatmapDeviceSignal(device="samy", signal="samy"),
device_z=HeatmapDeviceSignal(device="bpm4i", signal="bpm4i"),
color_map="viridis",
)
heatmap_widget.scan_item = create_dummy_scan_item()
@@ -407,7 +407,7 @@ def test_heatmap_settings_popup_accept_changes(heatmap_widget, qtbot):
"""
Test that changes made in the settings dialog are applied correctly.
"""
heatmap_widget.plot(x_name="samx", y_name="samy", z_name="bpm4i")
heatmap_widget.plot(device_x="samx", device_y="samy", device_z="bpm4i")
assert heatmap_widget.color_map == "plasma" # Default colormap
heatmap_widget.show_heatmap_settings()
qtbot.waitUntil(lambda: heatmap_widget.heatmap_dialog is not None)
@@ -431,7 +431,7 @@ def test_heatmap_settings_popup_show_settings(heatmap_widget, qtbot):
"""
Test that the settings dialog opens and contains the expected elements.
"""
heatmap_widget.plot(x_name="samx", y_name="samy", z_name="bpm4i")
heatmap_widget.plot(device_x="samx", device_y="samy", device_z="bpm4i")
heatmap_widget.show_heatmap_settings()
qtbot.waitUntil(lambda: heatmap_widget.heatmap_dialog is not None)
@@ -439,13 +439,13 @@ def test_heatmap_settings_popup_show_settings(heatmap_widget, qtbot):
assert dialog.isVisible()
assert dialog.widget is not None
assert hasattr(dialog.widget.ui, "color_map")
assert hasattr(dialog.widget.ui, "x_name")
assert hasattr(dialog.widget.ui, "y_name")
assert hasattr(dialog.widget.ui, "z_name")
assert hasattr(dialog.widget.ui, "device_x")
assert hasattr(dialog.widget.ui, "device_y")
assert hasattr(dialog.widget.ui, "device_z")
# Check that the ui elements are correctly initialized
assert dialog.widget.ui.color_map.colormap == heatmap_widget.color_map
assert dialog.widget.ui.x_name.currentText() == heatmap_widget._image_config.x_device.name
assert dialog.widget.ui.device_x.currentText() == heatmap_widget._image_config.device_x.device
dialog.reject()
qtbot.waitUntil(lambda: heatmap_widget.heatmap_dialog is None)
@@ -458,7 +458,7 @@ def test_heatmap_widget_reset(heatmap_widget):
heatmap_widget._pending_interpolation_request = object()
heatmap_widget._latest_interpolation_version = 5
heatmap_widget.scan_item = create_dummy_scan_item()
heatmap_widget.plot(x_name="samx", y_name="samy", z_name="bpm4i")
heatmap_widget.plot(device_x="samx", device_y="samy", device_z="bpm4i")
heatmap_widget.reset()
assert heatmap_widget._grid_index is None
@@ -476,12 +476,12 @@ def test_heatmap_widget_update_plot_with_scan_history(heatmap_widget, grid_scan_
heatmap_widget.client.history._scan_ids.append(grid_scan_history_msg.scan_id)
heatmap_widget.client.queue.scan_storage.current_scan = None
heatmap_widget.plot(
x_name="samx",
y_name="samy",
z_name="bpm4i",
x_entry="samx",
y_entry="samy",
z_entry="bpm4i",
device_x="samx",
device_y="samy",
device_z="bpm4i",
signal_x="samx",
signal_y="samy",
signal_z="bpm4i",
)
qtbot.waitUntil(lambda: heatmap_widget.main_image.raw_data is not None)
qtbot.waitUntil(lambda: heatmap_widget.main_image.raw_data.shape == (10, 10))
@@ -602,219 +602,219 @@ def test_finish_interpolation_thread_cleans_references(heatmap_widget):
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 == ""
assert heatmap_widget.device_x == ""
assert heatmap_widget.signal_x == ""
assert heatmap_widget.device_y == ""
assert heatmap_widget.signal_y == ""
assert heatmap_widget.device_z == ""
assert heatmap_widget.signal_z == ""
# Set devices via plot
heatmap_widget.plot(x_name="samx", y_name="samy", z_name="bpm4i")
heatmap_widget.plot(device_x="samx", device_y="samy", device_z="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
assert heatmap_widget.device_x == "samx"
assert heatmap_widget.signal_x # Should have some entry
assert heatmap_widget.device_y == "samy"
assert heatmap_widget.signal_y # Should have some entry
assert heatmap_widget.device_z == "bpm4i"
assert heatmap_widget.signal_z # 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 device_x - should auto-validate entry
heatmap_widget.device_x = "samx"
assert heatmap_widget._image_config.device_x is not None
assert heatmap_widget._image_config.device_x.device == "samx"
assert heatmap_widget._image_config.device_x.signal is not None # Entry should be validated
assert heatmap_widget.device_x == "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 device_y
heatmap_widget.device_y = "samy"
assert heatmap_widget._image_config.device_y is not None
assert heatmap_widget._image_config.device_y.device == "samy"
assert heatmap_widget._image_config.device_y.signal is not None
assert heatmap_widget.device_y == "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"
# Set device_z
heatmap_widget.device_z = "bpm4i"
assert heatmap_widget._image_config.device_z is not None
assert heatmap_widget._image_config.device_z.device == "bpm4i"
assert heatmap_widget._image_config.device_z.signal is not None
assert heatmap_widget.device_z == "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
heatmap_widget.device_x = "samx"
initial_entry = heatmap_widget.signal_x
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"
heatmap_widget.signal_x = "samx"
assert heatmap_widget._image_config.device_x.signal == "samx"
assert heatmap_widget.signal_x == "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"
heatmap_widget.device_y = "samy"
heatmap_widget.signal_y = "samy_setpoint"
assert heatmap_widget._image_config.device_y.signal == "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"
heatmap_widget.device_z = "bpm4i"
heatmap_widget.signal_z = "bpm4i"
assert heatmap_widget._image_config.device_z.signal == "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"
heatmap_widget.signal_x = "some_entry"
# Should not crash, entry should remain empty
assert heatmap_widget.x_device_entry == ""
assert heatmap_widget._image_config.x_device is None
assert heatmap_widget.signal_x == ""
assert heatmap_widget._image_config.device_x 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
heatmap_widget.device_x = "samx"
assert heatmap_widget._image_config.device_x 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
heatmap_widget.device_x = ""
assert heatmap_widget.device_x == ""
assert heatmap_widget._image_config.device_x 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"
heatmap_widget.device_x = "samx"
heatmap_widget.device_y = "samy"
heatmap_widget.device_z = "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
assert heatmap_widget._image_config.device_x is not None
assert heatmap_widget._image_config.device_y is not None
assert heatmap_widget._image_config.device_z 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"
heatmap_widget.device_x = "samx"
assert heatmap_widget.x_label == "samx"
# Set y device - should update y label
heatmap_widget.y_device_name = "samy"
heatmap_widget.device_y = "samy"
assert heatmap_widget.y_label == "samy"
# Set z device - should update title
heatmap_widget.z_device_name = "bpm4i"
heatmap_widget.device_z = "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 == ""
heatmap_widget.device_x = "samx"
assert heatmap_widget.device_x == "samx"
assert heatmap_widget.device_y == ""
assert heatmap_widget.device_z == ""
# 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 == ""
heatmap_widget.device_y = "samy"
assert heatmap_widget.device_x == "samx"
assert heatmap_widget.device_y == "samy"
assert heatmap_widget.device_z == ""
# 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
assert heatmap_widget._image_config.device_x is not None
assert heatmap_widget._image_config.device_y 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
assert "device_x" in Heatmap.USER_ACCESS
assert "device_x.setter" in Heatmap.USER_ACCESS
assert "signal_x" in Heatmap.USER_ACCESS
assert "signal_x.setter" in Heatmap.USER_ACCESS
assert "device_y" in Heatmap.USER_ACCESS
assert "device_y.setter" in Heatmap.USER_ACCESS
assert "signal_y" in Heatmap.USER_ACCESS
assert "signal_y.setter" in Heatmap.USER_ACCESS
assert "device_z" in Heatmap.USER_ACCESS
assert "device_z.setter" in Heatmap.USER_ACCESS
assert "signal_z" in Heatmap.USER_ACCESS
assert "signal_z.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
heatmap_widget.device_x = "samx"
initial_entry = heatmap_widget.signal_x
# 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"
heatmap_widget.signal_x = "samx" # Use same name as validated entry
assert heatmap_widget.signal_x == "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")
heatmap_widget.plot(device_x="samx", device_y="samy", device_z="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"
assert heatmap_widget.device_x == "samx"
assert heatmap_widget.device_y == "samy"
assert heatmap_widget.device_z == "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"
assert heatmap_widget.signal_x == "samx"
assert heatmap_widget.signal_y == "samy"
assert heatmap_widget.signal_z == "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")
heatmap_widget.plot(device_x="samx", device_y="samy", device_z="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"
heatmap_widget.device_x = "samz"
assert heatmap_widget.device_x == "samz"
assert heatmap_widget._image_config.device_x.device == "samz"
# Overwrite y device entry
heatmap_widget.y_device_entry = "samy"
assert heatmap_widget.y_device_entry == "samy"
heatmap_widget.signal_y = "samy"
assert heatmap_widget.signal_y == "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"
heatmap_widget.device_x = "samx"
heatmap_widget.device_y = "samy"
heatmap_widget.device_z = "bpm4i"
# Clear x device
heatmap_widget.x_device_name = ""
assert heatmap_widget.x_device_name == ""
assert heatmap_widget._image_config.x_device is None
heatmap_widget.device_x = ""
assert heatmap_widget.device_x == ""
assert heatmap_widget._image_config.device_x is None
# Y and Z should still be set
assert heatmap_widget.y_device_name == "samy"
assert heatmap_widget.z_device_name == "bpm4i"
assert heatmap_widget.device_y == "samy"
assert heatmap_widget.device_z == "bpm4i"
def test_device_properties_property_changed_signal(heatmap_widget):
@@ -826,12 +826,12 @@ def test_device_properties_property_changed_signal(heatmap_widget):
heatmap_widget.property_changed.connect(mock_handler)
# Set device name
heatmap_widget.x_device_name = "samx"
heatmap_widget.device_x = "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")
mock_handler.assert_any_call("device_x", "samx")
def test_auto_emit_syncs_heatmap_toolbar_actions(heatmap_widget):
@@ -855,7 +855,7 @@ def test_auto_emit_syncs_heatmap_toolbar_actions(heatmap_widget):
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"
heatmap_widget.device_x = "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
@@ -864,28 +864,28 @@ def test_device_entry_validation_with_invalid_device(heatmap_widget):
def test_device_properties_sequential_entry_changes(heatmap_widget):
"""Test changing device entry multiple times."""
# Set device
heatmap_widget.x_device_name = "samx"
heatmap_widget.device_x = "samx"
# Change entry multiple times
heatmap_widget.x_device_entry = "samx_velocity"
assert heatmap_widget.x_device_entry == "samx_velocity"
heatmap_widget.signal_x = "samx_velocity"
assert heatmap_widget.signal_x == "samx_velocity"
heatmap_widget.x_device_entry = "samx_setpoint"
assert heatmap_widget.x_device_entry == "samx_setpoint"
heatmap_widget.signal_x = "samx_setpoint"
assert heatmap_widget.signal_x == "samx_setpoint"
heatmap_widget.x_device_entry = "samx"
assert heatmap_widget.x_device_entry == "samx"
heatmap_widget.signal_x = "samx"
assert heatmap_widget.signal_x == "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 == ""
heatmap_widget.device_x = None
assert heatmap_widget.device_x == ""
# Set a device first
heatmap_widget.y_device_name = "samy"
heatmap_widget.device_y = "samy"
# Entry None should not change anything
heatmap_widget.y_device_entry = None
assert heatmap_widget.y_device_entry # Should still have validated entry
heatmap_widget.signal_y = None
assert heatmap_widget.signal_y # Should still have validated entry

View File

@@ -14,14 +14,9 @@ from tests.unit_tests.conftest import create_widget
def _set_signal_config(
client,
device_name: str,
signal_name: str,
signal_class: str,
ndim: int,
obj_name: str | None = None,
client, device: str, signal_name: str, signal_class: str, ndim: int, obj_name: str | None = None
):
device = client.device_manager.devices[device_name]
device = client.device_manager.devices[device]
device._info["signals"][signal_name] = {
"obj_name": obj_name or signal_name,
"signal_class": signal_class,
@@ -153,14 +148,14 @@ def test_image_setup_preview_signal_1d(qtbot, mocked_client):
obj_name="waveform1d_img",
)
view.image(device_name="waveform1d", device_entry="img")
view.image(device="waveform1d", signal="img")
# Subscriptions should indicate 1D preview connection
sub = view.subscriptions["main"]
assert sub.source == "device_monitor_1d"
assert sub.monitor_type == "1d"
assert view.device_name == "waveform1d"
assert view.device_entry == "img"
assert view.device == "waveform1d"
assert view.signal == "img"
# Simulate a waveform update from the dispatcher
waveform = np.arange(25, dtype=float)
@@ -187,14 +182,14 @@ def test_image_setup_preview_signal_2d(qtbot, mocked_client):
obj_name="eiger_img2d",
)
view.image(device_name="eiger", device_entry="img2d")
view.image(device="eiger", signal="img2d")
# Subscriptions should indicate 2D preview connection
sub = view.subscriptions["main"]
assert sub.source == "device_monitor_2d"
assert sub.monitor_type == "2d"
assert view.device_name == "eiger"
assert view.device_entry == "img2d"
assert view.device == "eiger"
assert view.signal == "img2d"
# Simulate a 2D image update
test_data = np.arange(16, dtype=float).reshape(4, 4)
@@ -259,7 +254,7 @@ def test_image_async_signal_uses_obj_name(qtbot, mocked_client, monkeypatch):
mocked_client, "eiger", "img", signal_class="AsyncSignal", ndim=1, obj_name="async_obj"
)
view.image(device_name="eiger", device_entry="img")
view.image(device="eiger", signal="img")
assert view.subscriptions["main"].async_signal_name == "async_obj"
assert view.async_update is True
@@ -300,7 +295,7 @@ def test_disconnect_clears_async_state(qtbot, mocked_client, monkeypatch):
mocked_client, "eiger", "img", signal_class="AsyncSignal", ndim=2, obj_name="async_obj"
)
view.image(device_name="eiger", device_entry="img")
view.image(device="eiger", signal="img")
view.scan_id = "scan_x"
view.old_scan_id = "scan_y"
view.subscriptions["main"].async_signal_name = "async_obj"
@@ -308,7 +303,7 @@ def test_disconnect_clears_async_state(qtbot, mocked_client, monkeypatch):
# Avoid touching real dispatcher
monkeypatch.setattr(view.bec_dispatcher, "disconnect_slot", lambda *args, **kwargs: None)
view.disconnect_monitor(device_name="eiger", device_entry="img")
view.disconnect_monitor(device="eiger", signal="img")
assert view.subscriptions["main"].async_signal_name is None
assert view.async_update is False
@@ -322,7 +317,7 @@ def test_image_setup_rejects_unsupported_signal_class(qtbot, mocked_client):
view = create_widget(qtbot, Image, client=mocked_client)
_set_signal_config(mocked_client, "eiger", "img", signal_class="Signal", ndim=2)
view.image(device_name="eiger", device_entry="img")
view.image(device="eiger", signal="img")
assert view.subscriptions["main"].source is None
assert view.subscriptions["main"].monitor_type is None
@@ -333,13 +328,13 @@ def test_image_disconnects_with_missing_entry(qtbot, mocked_client):
view = create_widget(qtbot, Image, client=mocked_client)
_set_signal_config(mocked_client, "eiger", "img", signal_class="PreviewSignal", ndim=2)
view.image(device_name="eiger", device_entry="img")
assert view.device_name == "eiger"
assert view.device_entry == "img"
view.image(device="eiger", signal="img")
assert view.device == "eiger"
assert view.signal == "img"
view.image(device_name="eiger", device_entry=None)
assert view.device_name == ""
assert view.device_entry == ""
view.image(device="eiger", signal=None)
assert view.device == ""
assert view.signal == ""
def test_handle_scan_change_clears_buffers_and_resets_crosshair(qtbot, mocked_client, monkeypatch):
@@ -541,8 +536,8 @@ def test_setup_image_from_toolbar(qtbot, mocked_client, monkeypatch):
bec_image_view.on_device_selection_changed(None)
qtbot.wait(200)
assert bec_image_view.device_name == "eiger"
assert bec_image_view.device_entry == "img"
assert bec_image_view.device == "eiger"
assert bec_image_view.signal == "img"
assert bec_image_view.subscriptions["main"].source == "device_monitor_2d"
assert bec_image_view.subscriptions["main"].monitor_type == "2d"
assert bec_image_view.main_image.raw_data is None
@@ -834,8 +829,8 @@ def test_device_selection_syncs_from_properties(qtbot, mocked_client, monkeypatc
),
)
view.device_name = "eiger"
view.device_entry = "img2d"
view.device = "eiger"
view.signal = "img2d"
qtbot.wait(200) # Allow signal processing
@@ -847,19 +842,19 @@ def test_device_selection_syncs_from_properties(qtbot, mocked_client, monkeypatc
)
def test_device_entry_syncs_from_toolbar(qtbot, mocked_client):
def test_signal_syncs_from_toolbar(qtbot, mocked_client):
view = create_widget(qtbot, Image, client=mocked_client)
_set_signal_config(mocked_client, "eiger", "img_a", signal_class="PreviewSignal", ndim=2)
_set_signal_config(mocked_client, "eiger", "img_b", signal_class="PreviewSignal", ndim=2)
view.device_name = "eiger"
view.device_entry = "img_a"
view.device = "eiger"
view.signal = "img_a"
device_selection = view.toolbar.components.get_action("device_selection").widget
device_selection.signal_combo_box.blockSignals(True)
device_selection.signal_combo_box.setCurrentText("img_b")
device_selection.signal_combo_box.blockSignals(False)
view._sync_device_entry_from_toolbar()
view._sync_signal_from_toolbar()
assert view.device_entry == "img_b"
assert view.signal == "img_b"

View File

@@ -23,12 +23,12 @@ def test_motor_map_select_motor(qtbot, mocked_client):
"""Test selecting motors for the motor map."""
mm = create_widget(qtbot, MotorMap, client=mocked_client)
mm.map(x_name="samx", y_name="samy", validate_bec=True)
mm.map(device_x="samx", device_y="samy", validate_bec=True)
assert mm.config.x_motor.name == "samx"
assert mm.config.y_motor.name == "samy"
assert mm.config.x_motor.limits == [-10, 10]
assert mm.config.y_motor.limits == [-5, 5]
assert mm.config.device_x.device == "samx"
assert mm.config.device_y.device == "samy"
assert mm.config.device_x.limits == [-10, 10]
assert mm.config.device_y.limits == [-5, 5]
assert mm.config.scatter_size == 5
assert mm.config.max_points == 5000
assert mm.config.num_dim_points == 100
@@ -39,7 +39,7 @@ def test_motor_map_select_motor(qtbot, mocked_client):
def test_motor_map_properties(qtbot, mocked_client):
"""Test setting and getting properties of MotorMap."""
mm = create_widget(qtbot, MotorMap, client=mocked_client)
mm.map(x_name="samx", y_name="samy")
mm.map(device_x="samx", device_y="samy")
# Test color property
mm.color = (100, 150, 200, 255)
@@ -86,7 +86,7 @@ def test_motor_map_properties(qtbot, mocked_client):
def test_motor_map_get_limits(qtbot, mocked_client):
"""Test getting motor limits."""
mm = create_widget(qtbot, MotorMap, client=mocked_client)
mm.map(x_name="samx", y_name="samy")
mm.map(device_x="samx", device_y="samy")
expected_limits = {"samx": [-10, 10], "samy": [-5, 5]}
for motor_name, expected_limit in expected_limits.items():
@@ -133,7 +133,7 @@ def test_motor_map_reset_history(qtbot, mocked_client):
def test_motor_map_on_device_readback(qtbot, mocked_client):
"""Test the motor map updates when receiving device readback."""
mm = create_widget(qtbot, MotorMap, client=mocked_client)
mm.map(x_name="samx", y_name="samy")
mm.map(device_x="samx", device_y="samy")
# Clear the buffer and add initial position
mm._buffer = {"x": [1.0], "y": [2.0]}
@@ -161,7 +161,7 @@ def test_motor_map_on_device_readback(qtbot, mocked_client):
def test_motor_map_max_points_limit(qtbot, mocked_client):
"""Test that the buffer doesn't exceed max_points."""
mm = create_widget(qtbot, MotorMap, client=mocked_client)
mm.map(x_name="samx", y_name="samy")
mm.map(device_x="samx", device_y="samy")
# Add more points than max_points
mm._buffer = {"x": [1.0, 2.0, 3.0, 4.0], "y": [5.0, 6.0, 7.0, 8.0]}
@@ -219,7 +219,7 @@ def test_motor_map_limit_map(qtbot, mocked_client):
def test_motor_map_change_limits(qtbot, mocked_client):
mm = create_widget(qtbot, MotorMap, client=mocked_client)
mm.map(x_name="samx", y_name="samy")
mm.map(device_x="samx", device_y="samy")
# Original mocked limits are
# samx: [-10, 10]
@@ -229,8 +229,8 @@ def test_motor_map_change_limits(qtbot, mocked_client):
rect = mm._limit_map.boundingRect()
assert rect.width() == 20 # -10 to 10 inclusive
assert rect.height() == 10 # -5 to 5 inclusive
assert mm.config.x_motor.limits == [-10, 10]
assert mm.config.y_motor.limits == [-5, 5]
assert mm.config.device_x.limits == [-10, 10]
assert mm.config.device_y.limits == [-5, 5]
# Change the limits of the samx motor
mm.dev["samx"].limits = [-20, 20]
@@ -239,8 +239,8 @@ def test_motor_map_change_limits(qtbot, mocked_client):
qtbot.wait(200) # Allow time for the update to process
# Check that the limits map was updated
assert mm.config.x_motor.limits == [-20, 20]
assert mm.config.y_motor.limits == [-5, 5]
assert mm.config.device_x.limits == [-20, 20]
assert mm.config.device_y.limits == [-5, 5]
rect = mm._limit_map.boundingRect()
assert rect.width() == 40 # -20 to 20 inclusive
assert rect.height() == 10 # -5 to 5 inclusive -> same as before
@@ -276,13 +276,13 @@ def test_motor_map_toolbar_selection(qtbot, mocked_client):
motor_selection.widget.motor_x.setCurrentText("samx")
motor_selection.widget.motor_y.setCurrentText("samy")
assert mm.config.x_motor.name == "samx"
assert mm.config.y_motor.name == "samy"
assert mm.config.device_x.device == "samx"
assert mm.config.device_y.device == "samy"
motor_selection.widget.motor_y.setCurrentText("samz")
assert mm.config.x_motor.name == "samx"
assert mm.config.y_motor.name == "samz"
assert mm.config.device_x.device == "samx"
assert mm.config.device_y.device == "samz"
def test_motor_selection_set_motors_blocks_signals(qtbot, mocked_client):
@@ -306,19 +306,19 @@ def test_motor_properties_partial_then_complete_map(qtbot, mocked_client):
mm = create_widget(qtbot, MotorMap, client=mocked_client)
spy = QSignalSpy(mm.property_changed)
mm.x_motor = "samx"
mm.device_x = "samx"
assert mm.config.x_motor.name == "samx"
assert mm.config.y_motor.name is None
assert mm.config.device_x.device == "samx"
assert mm.config.device_y.device is None
assert mm._trace is None # map not triggered yet
assert spy.at(0) == ["x_motor", "samx"]
assert spy.at(0) == ["device_x", "samx"]
mm.y_motor = "samy"
mm.device_y = "samy"
assert mm.config.x_motor.name == "samx"
assert mm.config.y_motor.name == "samy"
assert mm.config.device_x.device == "samx"
assert mm.config.device_y.device == "samy"
assert mm._trace is not None # map called once both valid
assert spy.at(1) == ["y_motor", "samy"]
assert spy.at(1) == ["device_y", "samy"]
assert len(mm._buffer["x"]) == 1
assert len(mm._buffer["y"]) == 1
@@ -331,9 +331,9 @@ def test_set_motor_name_emits_and_syncs_toolbar(qtbot, mocked_client):
spy = QSignalSpy(mm.property_changed)
mm._set_motor_name("x", "samx")
assert mm.config.x_motor.name == "samx"
assert mm.config.device_x.device == "samx"
assert motor_selection.motor_x.currentText() == "samx"
assert spy.at(0) == ["x_motor", "samx"]
assert spy.at(0) == ["device_x", "samx"]
# Calling with same name should be a no-op
initial_count = spy.count()
@@ -350,7 +350,7 @@ def test_motor_map_settings_dialog(qtbot, mocked_client):
assert action_ref().action.isVisible()
# set properties to be fetched by dialog
mm.map(x_name="samx", y_name="samy")
mm.map(device_x="samx", device_y="samy")
mm.precision = 2
mm.max_points = 1000
mm.scatter_size = 10

View File

@@ -37,7 +37,7 @@ def test_scatter_waveform_plot(qtbot, mocked_client):
assert curve is not None
assert isinstance(curve.config, ScatterCurveConfig)
assert curve.config.x_device == ScatterDeviceSignal(name="samx", entry="samx")
assert curve.config.device_x == ScatterDeviceSignal(device="samx", signal="samx")
assert curve.config.label == "bpm4i-bpm4i"
@@ -144,49 +144,49 @@ def test_device_safe_properties_get(qtbot, mocked_client):
swf = create_widget(qtbot, ScatterWaveform, client=mocked_client)
# Initially devices should be empty
assert swf.x_device_name == ""
assert swf.x_device_entry == ""
assert swf.y_device_name == ""
assert swf.y_device_entry == ""
assert swf.z_device_name == ""
assert swf.z_device_entry == ""
assert swf.device_x == ""
assert swf.signal_x == ""
assert swf.device_y == ""
assert swf.signal_y == ""
assert swf.device_z == ""
assert swf.signal_z == ""
# Set devices via plot
swf.plot(x_name="samx", y_name="samy", z_name="bpm4i")
swf.plot(device_x="samx", device_y="samy", device_z="bpm4i")
# Check properties return device names and entries separately
assert swf.x_device_name == "samx"
assert swf.x_device_entry # Should have some entry
assert swf.y_device_name == "samy"
assert swf.y_device_entry # Should have some entry
assert swf.z_device_name == "bpm4i"
assert swf.z_device_entry # Should have some entry
assert swf.device_x == "samx"
assert swf.signal_x # Should have some entry
assert swf.device_y == "samy"
assert swf.signal_y # Should have some entry
assert swf.device_z == "bpm4i"
assert swf.signal_z # Should have some entry
def test_device_safe_properties_set_name(qtbot, mocked_client):
"""Test that device SafeProperty setters work for device names."""
swf = create_widget(qtbot, ScatterWaveform, client=mocked_client)
# Set x_device_name - should auto-validate entry
swf.x_device_name = "samx"
assert swf._main_curve.config.x_device is not None
assert swf._main_curve.config.x_device.name == "samx"
assert swf._main_curve.config.x_device.entry is not None # Entry should be validated
assert swf.x_device_name == "samx"
# Set device_x - should auto-validate entry
swf.device_x = "samx"
assert swf._main_curve.config.device_x is not None
assert swf._main_curve.config.device_x.device == "samx"
assert swf._main_curve.config.device_x.signal is not None # Entry should be validated
assert swf.device_x == "samx"
# Set y_device_name
swf.y_device_name = "samy"
assert swf._main_curve.config.y_device is not None
assert swf._main_curve.config.y_device.name == "samy"
assert swf._main_curve.config.y_device.entry is not None
assert swf.y_device_name == "samy"
# Set device_y
swf.device_y = "samy"
assert swf._main_curve.config.device_y is not None
assert swf._main_curve.config.device_y.device == "samy"
assert swf._main_curve.config.device_y.signal is not None
assert swf.device_y == "samy"
# Set z_device_name
swf.z_device_name = "bpm4i"
assert swf._main_curve.config.z_device is not None
assert swf._main_curve.config.z_device.name == "bpm4i"
assert swf._main_curve.config.z_device.entry is not None
assert swf.z_device_name == "bpm4i"
# Set device_z
swf.device_z = "bpm4i"
assert swf._main_curve.config.device_z is not None
assert swf._main_curve.config.device_z.device == "bpm4i"
assert swf._main_curve.config.device_z.signal is not None
assert swf.device_z == "bpm4i"
def test_device_safe_properties_set_entry(qtbot, mocked_client):
@@ -194,24 +194,24 @@ def test_device_safe_properties_set_entry(qtbot, mocked_client):
swf = create_widget(qtbot, ScatterWaveform, client=mocked_client)
# Set device name first - this auto-validates entry
swf.x_device_name = "samx"
initial_entry = swf.x_device_entry
swf.device_x = "samx"
initial_entry = swf.signal_x
assert initial_entry # Should have auto-validated entry
# Override with specific entry
swf.x_device_entry = "samx"
assert swf._main_curve.config.x_device.entry == "samx"
assert swf.x_device_entry == "samx"
swf.signal_x = "samx"
assert swf._main_curve.config.device_x.signal == "samx"
assert swf.signal_x == "samx"
# Same for y device
swf.y_device_name = "samy"
swf.y_device_entry = "samy_setpoint"
assert swf._main_curve.config.y_device.entry == "samy_setpoint"
swf.device_y = "samy"
swf.signal_y = "samy_setpoint"
assert swf._main_curve.config.device_y.signal == "samy_setpoint"
# Same for z device
swf.z_device_name = "bpm4i"
swf.z_device_entry = "bpm4i"
assert swf._main_curve.config.z_device.entry == "bpm4i"
swf.device_z = "bpm4i"
swf.signal_z = "bpm4i"
assert swf._main_curve.config.device_z.signal == "bpm4i"
def test_device_entry_cannot_be_set_without_name(qtbot, mocked_client):
@@ -219,10 +219,10 @@ def test_device_entry_cannot_be_set_without_name(qtbot, mocked_client):
swf = create_widget(qtbot, ScatterWaveform, client=mocked_client)
# Try to set entry without device name
swf.x_device_entry = "some_entry"
swf.signal_x = "some_entry"
# Should not crash, entry should remain empty
assert swf.x_device_entry == ""
assert swf._main_curve.config.x_device is None
assert swf.signal_x == ""
assert swf._main_curve.config.device_x is None
def test_device_safe_properties_set_empty(qtbot, mocked_client):
@@ -230,13 +230,13 @@ def test_device_safe_properties_set_empty(qtbot, mocked_client):
swf = create_widget(qtbot, ScatterWaveform, client=mocked_client)
# Set device first
swf.x_device_name = "samx"
assert swf._main_curve.config.x_device is not None
swf.device_x = "samx"
assert swf._main_curve.config.device_x is not None
# Set to empty string - should clear the device
swf.x_device_name = ""
assert swf.x_device_name == ""
assert swf._main_curve.config.x_device is None
swf.device_x = ""
assert swf.device_x == ""
assert swf._main_curve.config.device_x is None
def test_device_safe_properties_auto_plot(qtbot, mocked_client):
@@ -244,14 +244,14 @@ def test_device_safe_properties_auto_plot(qtbot, mocked_client):
swf = create_widget(qtbot, ScatterWaveform, client=mocked_client)
# Set all three devices
swf.x_device_name = "samx"
swf.y_device_name = "samy"
swf.z_device_name = "bpm4i"
swf.device_x = "samx"
swf.device_y = "samy"
swf.device_z = "bpm4i"
# Check that plot was called (config should be updated)
assert swf._main_curve.config.x_device is not None
assert swf._main_curve.config.y_device is not None
assert swf._main_curve.config.z_device is not None
assert swf._main_curve.config.device_x is not None
assert swf._main_curve.config.device_y is not None
assert swf._main_curve.config.device_z is not None
def test_device_properties_update_labels(qtbot, mocked_client):
@@ -259,11 +259,11 @@ def test_device_properties_update_labels(qtbot, mocked_client):
swf = create_widget(qtbot, ScatterWaveform, client=mocked_client)
# Set x device - should update x label
swf.x_device_name = "samx"
swf.device_x = "samx"
assert swf.x_label == "samx"
# Set y device - should update y label
swf.y_device_name = "samy"
swf.device_y = "samy"
assert swf.y_label == "samy"
# Note: ScatterWaveform doesn't have a title like Heatmap does for z_device
@@ -274,39 +274,39 @@ def test_device_properties_partial_configuration(qtbot, mocked_client):
swf = create_widget(qtbot, ScatterWaveform, client=mocked_client)
# Set only x device
swf.x_device_name = "samx"
assert swf.x_device_name == "samx"
assert swf.y_device_name == ""
assert swf.z_device_name == ""
swf.device_x = "samx"
assert swf.device_x == "samx"
assert swf.device_y == ""
assert swf.device_z == ""
# Set only y device (x already set)
swf.y_device_name = "samy"
assert swf.x_device_name == "samx"
assert swf.y_device_name == "samy"
assert swf.z_device_name == ""
swf.device_y = "samy"
assert swf.device_x == "samx"
assert swf.device_y == "samy"
assert swf.device_z == ""
# Auto-plot should not trigger yet (z missing)
# But devices should be configured
assert swf._main_curve.config.x_device is not None
assert swf._main_curve.config.y_device is not None
assert swf._main_curve.config.device_x is not None
assert swf._main_curve.config.device_y is not None
def test_device_properties_in_user_access(qtbot, mocked_client):
"""Test that device properties are exposed in USER_ACCESS for RPC."""
swf = create_widget(qtbot, ScatterWaveform, client=mocked_client)
assert "x_device_name" in ScatterWaveform.USER_ACCESS
assert "x_device_name.setter" in ScatterWaveform.USER_ACCESS
assert "x_device_entry" in ScatterWaveform.USER_ACCESS
assert "x_device_entry.setter" in ScatterWaveform.USER_ACCESS
assert "y_device_name" in ScatterWaveform.USER_ACCESS
assert "y_device_name.setter" in ScatterWaveform.USER_ACCESS
assert "y_device_entry" in ScatterWaveform.USER_ACCESS
assert "y_device_entry.setter" in ScatterWaveform.USER_ACCESS
assert "z_device_name" in ScatterWaveform.USER_ACCESS
assert "z_device_name.setter" in ScatterWaveform.USER_ACCESS
assert "z_device_entry" in ScatterWaveform.USER_ACCESS
assert "z_device_entry.setter" in ScatterWaveform.USER_ACCESS
assert "device_x" in ScatterWaveform.USER_ACCESS
assert "device_x.setter" in ScatterWaveform.USER_ACCESS
assert "signal_x" in ScatterWaveform.USER_ACCESS
assert "signal_x.setter" in ScatterWaveform.USER_ACCESS
assert "device_y" in ScatterWaveform.USER_ACCESS
assert "device_y.setter" in ScatterWaveform.USER_ACCESS
assert "signal_y" in ScatterWaveform.USER_ACCESS
assert "signal_y.setter" in ScatterWaveform.USER_ACCESS
assert "device_z" in ScatterWaveform.USER_ACCESS
assert "device_z.setter" in ScatterWaveform.USER_ACCESS
assert "signal_z" in ScatterWaveform.USER_ACCESS
assert "signal_z.setter" in ScatterWaveform.USER_ACCESS
def test_device_properties_validation(qtbot, mocked_client):
@@ -314,15 +314,15 @@ def test_device_properties_validation(qtbot, mocked_client):
swf = create_widget(qtbot, ScatterWaveform, client=mocked_client)
# Set device name - entry should be auto-validated
swf.x_device_name = "samx"
initial_entry = swf.x_device_entry
swf.device_x = "samx"
initial_entry = swf.signal_x
# The entry should be validated (will be "samx" in the mock)
assert initial_entry == "samx"
# Set a different entry - should also be validated
swf.x_device_entry = "samx" # Use same name as validated entry
assert swf.x_device_entry == "samx"
swf.signal_x = "samx" # Use same name as validated entry
assert swf.signal_x == "samx"
def test_device_properties_with_plot_method(qtbot, mocked_client):
@@ -330,17 +330,17 @@ def test_device_properties_with_plot_method(qtbot, mocked_client):
swf = create_widget(qtbot, ScatterWaveform, client=mocked_client)
# Use plot method
swf.plot(x_name="samx", y_name="samy", z_name="bpm4i")
swf.plot(device_x="samx", device_y="samy", device_z="bpm4i")
# Properties should reflect the plotted devices
assert swf.x_device_name == "samx"
assert swf.y_device_name == "samy"
assert swf.z_device_name == "bpm4i"
assert swf.device_x == "samx"
assert swf.device_y == "samy"
assert swf.device_z == "bpm4i"
# Entries should be validated
assert swf.x_device_entry == "samx"
assert swf.y_device_entry == "samy"
assert swf.z_device_entry == "bpm4i"
assert swf.signal_x == "samx"
assert swf.signal_y == "samy"
assert swf.signal_z == "bpm4i"
def test_device_properties_overwrite_via_properties(qtbot, mocked_client):
@@ -348,16 +348,16 @@ def test_device_properties_overwrite_via_properties(qtbot, mocked_client):
swf = create_widget(qtbot, ScatterWaveform, client=mocked_client)
# First set via plot
swf.plot(x_name="samx", y_name="samy", z_name="bpm4i")
swf.plot(device_x="samx", device_y="samy", device_z="bpm4i")
# Overwrite x device via properties
swf.x_device_name = "samz"
assert swf.x_device_name == "samz"
assert swf._main_curve.config.x_device.name == "samz"
swf.device_x = "samz"
assert swf.device_x == "samz"
assert swf._main_curve.config.device_x.device == "samz"
# Overwrite y device entry
swf.y_device_entry = "samy"
assert swf.y_device_entry == "samy"
swf.signal_y = "samy"
assert swf.signal_y == "samy"
def test_device_properties_clearing_devices(qtbot, mocked_client):
@@ -365,18 +365,18 @@ def test_device_properties_clearing_devices(qtbot, mocked_client):
swf = create_widget(qtbot, ScatterWaveform, client=mocked_client)
# Set all devices
swf.x_device_name = "samx"
swf.y_device_name = "samy"
swf.z_device_name = "bpm4i"
swf.device_x = "samx"
swf.device_y = "samy"
swf.device_z = "bpm4i"
# Clear x device
swf.x_device_name = ""
assert swf.x_device_name == ""
assert swf._main_curve.config.x_device is None
swf.device_x = ""
assert swf.device_x == ""
assert swf._main_curve.config.device_x is None
# Y and Z should still be set
assert swf.y_device_name == "samy"
assert swf.z_device_name == "bpm4i"
assert swf.device_y == "samy"
assert swf.device_z == "bpm4i"
def test_device_properties_property_changed_signal(qtbot, mocked_client):
@@ -390,12 +390,12 @@ def test_device_properties_property_changed_signal(qtbot, mocked_client):
swf.property_changed.connect(mock_handler)
# Set device name
swf.x_device_name = "samx"
swf.device_x = "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")
mock_handler.assert_any_call("device_x", "samx")
def test_device_entry_validation_with_invalid_device(qtbot, mocked_client):
@@ -403,7 +403,7 @@ def test_device_entry_validation_with_invalid_device(qtbot, mocked_client):
swf = create_widget(qtbot, ScatterWaveform, client=mocked_client)
# Try to set invalid device name
swf.x_device_name = "nonexistent_device"
swf.device_x = "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
@@ -414,17 +414,17 @@ def test_device_properties_sequential_entry_changes(qtbot, mocked_client):
swf = create_widget(qtbot, ScatterWaveform, client=mocked_client)
# Set device
swf.x_device_name = "samx"
swf.device_x = "samx"
# Change entry multiple times
swf.x_device_entry = "samx_velocity"
assert swf.x_device_entry == "samx_velocity"
swf.signal_x = "samx_velocity"
assert swf.signal_x == "samx_velocity"
swf.x_device_entry = "samx_setpoint"
assert swf.x_device_entry == "samx_setpoint"
swf.signal_x = "samx_setpoint"
assert swf.signal_x == "samx_setpoint"
swf.x_device_entry = "samx"
assert swf.x_device_entry == "samx"
swf.signal_x = "samx"
assert swf.signal_x == "samx"
def test_device_properties_with_none_values(qtbot, mocked_client):
@@ -432,15 +432,15 @@ def test_device_properties_with_none_values(qtbot, mocked_client):
swf = create_widget(qtbot, ScatterWaveform, client=mocked_client)
# Device name None should be treated as empty
swf.x_device_name = None
assert swf.x_device_name == ""
swf.device_x = None
assert swf.device_x == ""
# Set a device first
swf.y_device_name = "samy"
swf.device_y = "samy"
# Entry None should not change anything
swf.y_device_entry = None
assert swf.y_device_entry # Should still have validated entry
swf.signal_y = None
assert swf.signal_y # Should still have validated entry
################################################################################
@@ -457,9 +457,9 @@ def test_scatter_curve_settings_accept_changes(qtbot, mocked_client):
qtbot.addWidget(settings)
# Set up the widgets with test values
settings.ui.x_name.set_device("samx")
settings.ui.y_name.set_device("samy")
settings.ui.z_name.set_device("bpm4i")
settings.ui.device_x.set_device("samx")
settings.ui.device_y.set_device("samy")
settings.ui.device_z.set_device("bpm4i")
# Mock the plot method to verify it gets called with correct arguments
with patch.object(swf, "plot") as mock_plot:
@@ -472,9 +472,9 @@ def test_scatter_curve_settings_accept_changes(qtbot, mocked_client):
call_kwargs = mock_plot.call_args[1]
# Verify device names were extracted correctly
assert call_kwargs["x_name"] == "samx"
assert call_kwargs["y_name"] == "samy"
assert call_kwargs["z_name"] == "bpm4i"
assert call_kwargs["device_x"] == "samx"
assert call_kwargs["device_y"] == "samy"
assert call_kwargs["device_z"] == "bpm4i"
def test_scatter_curve_settings_accept_changes_with_entries(qtbot, mocked_client):
@@ -486,9 +486,9 @@ def test_scatter_curve_settings_accept_changes_with_entries(qtbot, mocked_client
qtbot.addWidget(settings)
# Set devices first to populate signal comboboxes
settings.ui.x_name.set_device("samx")
settings.ui.y_name.set_device("samy")
settings.ui.z_name.set_device("bpm4i")
settings.ui.device_x.set_device("samx")
settings.ui.device_y.set_device("samy")
settings.ui.device_z.set_device("bpm4i")
qtbot.wait(100) # Allow time for signals to populate
# Mock the plot method
@@ -499,9 +499,9 @@ def test_scatter_curve_settings_accept_changes_with_entries(qtbot, mocked_client
call_kwargs = mock_plot.call_args[1]
# Verify entries are extracted (will use get_signal_name())
assert "x_entry" in call_kwargs
assert "y_entry" in call_kwargs
assert "z_entry" in call_kwargs
assert "signal_x" in call_kwargs
assert "signal_y" in call_kwargs
assert "signal_z" in call_kwargs
def test_scatter_curve_settings_accept_changes_color_map(qtbot, mocked_client):
@@ -514,9 +514,9 @@ def test_scatter_curve_settings_accept_changes_color_map(qtbot, mocked_client):
qtbot.addWidget(settings)
# Set devices
settings.ui.x_name.set_device("samx")
settings.ui.y_name.set_device("samy")
settings.ui.z_name.set_device("bpm4i")
settings.ui.device_x.set_device("samx")
settings.ui.device_y.set_device("samy")
settings.ui.device_z.set_device("bpm4i")
# Get the current colormap
color_map = settings.ui.color_map.colormap
@@ -532,13 +532,13 @@ def test_scatter_curve_settings_fetch_all_properties(qtbot, mocked_client):
swf = create_widget(qtbot, ScatterWaveform, client=mocked_client)
# First set up the scatter waveform with some data
swf.plot(x_name="samx", y_name="samy", z_name="bpm4i")
swf.plot(device_x="samx", device_y="samy", device_z="bpm4i")
# Create the settings widget - it should fetch properties automatically
settings = ScatterCurveSettings(parent=None, target_widget=swf, popup=True)
qtbot.addWidget(settings)
# Verify the settings widget has fetched the values
assert settings.ui.x_name.currentText() == "samx"
assert settings.ui.y_name.currentText() == "samy"
assert settings.ui.z_name.currentText() == "bpm4i"
assert settings.ui.device_x.currentText() == "samx"
assert settings.ui.device_y.currentText() == "samy"
assert settings.ui.device_z.currentText() == "bpm4i"

View File

@@ -107,8 +107,8 @@ def test_plot_single_arg_input_sync(qtbot, mocked_client):
assert c1.config.source == "device"
assert c2.config.source == "device"
assert c1.config.signal == DeviceSignal(name="bpm4i", entry="bpm4i", dap=None)
assert c2.config.signal == DeviceSignal(name="bpm3a", entry="bpm3a", dap=None)
assert c1.config.signal == DeviceSignal(device="bpm4i", signal="bpm4i", dap=None)
assert c2.config.signal == DeviceSignal(device="bpm3a", signal="bpm3a", dap=None)
# Check that the curve is added to the plot
assert len(wf.plot_item.curves) == 2
@@ -122,8 +122,8 @@ def test_plot_single_arg_input_async(qtbot, mocked_client):
assert c1.config.source == "device"
assert c2.config.source == "device"
assert c1.config.signal == DeviceSignal(name="eiger", entry="eiger", dap=None)
assert c2.config.signal == DeviceSignal(name="async_device", entry="async_device", dap=None)
assert c1.config.signal == DeviceSignal(device="eiger", signal="eiger", dap=None)
assert c2.config.signal == DeviceSignal(device="async_device", signal="async_device", dap=None)
# Check that the curve is added to the plot
assert len(wf.plot_item.curves) == 2
@@ -305,7 +305,7 @@ def test_curve_json_setter_ignores_custom(qtbot, mocked_client):
"label": "device_curve",
"color": "#ff0000",
"source": "device",
"signal": {"name": "bpm4i", "entry": "bpm4i", "dap": None},
"signal": {"device": "bpm4i", "signal": "bpm4i", "dap": None},
}
custom_curve_config = {
"widget_class": "Curve",
@@ -475,7 +475,7 @@ def test_add_dap_curve(qtbot, mocked_client_with_dap, monkeypatch):
dap_curve = wf.add_dap_curve(device_label="bpm4i-bpm4i", dap_name="GaussianModel")
assert dap_curve is not None
assert dap_curve.config.source == "dap"
assert dap_curve.config.signal.name == "bpm4i"
assert dap_curve.config.signal.device == "bpm4i"
assert dap_curve.config.signal.dap == "GaussianModel"
@@ -491,8 +491,8 @@ def test_add_dap_curve_custom_source(qtbot, mocked_client_with_dap):
dap_curve = wf.add_dap_curve(device_label=custom_curve.name(), dap_name="GaussianModel")
assert dap_curve.config.source == "dap"
assert dap_curve.config.parent_label == custom_curve.name()
assert dap_curve.config.signal.name == custom_curve.name()
assert dap_curve.config.signal.entry == "custom"
assert dap_curve.config.signal.device == custom_curve.name()
assert dap_curve.config.signal.signal == "custom"
assert dap_curve.config.signal.dap == "GaussianModel"
@@ -764,7 +764,7 @@ def test_curve_set_data_error_non_custom(qtbot, mocked_client):
Test that calling set_data on a non-custom (device) curve raises a ValueError.
"""
wf = create_widget(qtbot, Waveform, client=mocked_client)
# Create a device curve by providing y_name (which makes source 'device')
# Create a device curve by providing device_y (which makes source 'device')
# Assume that entry_validator returns a valid entry.
c = wf.plot(arg1="bpm4i", label="device_curve")
with pytest.raises(ValueError):
@@ -1136,20 +1136,20 @@ def test_update_with_scan_history_by_index(qtbot, mocked_client, scan_history_fa
assert len(wf.client.history._scan_ids) == 2, "Expected two history scans"
# Do history curve plotting
wf.plot(y_name="bpm4i", y_entry="bpm4i", scan_id="hist1")
wf.plot(y_name="bpm4i", scan_number=2)
wf.plot(device_y="bpm4i", signal_y="bpm4i", scan_id="hist1")
wf.plot(device_y="bpm4i", scan_number=2)
assert len(wf.plot_item.curves) == 2, "Expected two curves for history scans"
c1, c2 = wf.plot_item.curves
# First curve should be for hist1, second for hist2
assert c1.config.signal.name == "bpm4i"
assert c1.config.signal.entry == "bpm4i"
assert c1.config.signal.device == "bpm4i"
assert c1.config.signal.signal == "bpm4i"
assert c1.config.scan_id == "hist1"
assert c1.config.scan_number == 1
assert c1.name() == "bpm4i-bpm4i-scan-1"
assert c2.config.signal.name == "bpm4i"
assert c2.config.signal.entry == "bpm4i"
assert c2.config.signal.device == "bpm4i"
assert c2.config.signal.signal == "bpm4i"
assert c2.config.scan_id == "hist2"
assert c2.config.scan_number == 2
assert c2.name() == "bpm4i-bpm4i-scan-2"
@@ -1163,7 +1163,7 @@ def test_history_curve_x_modes_pre_plot(qtbot, mocked_client, scan_history_facto
wf = create_widget(qtbot, Waveform, client=mocked_client)
hist1, hist2 = inject_scan_history(wf, scan_history_factory, ("hist1", 1), ("hist2", 2))
wf.x_mode = mode
c = wf.plot(y_name="bpm4i", y_entry="bpm4i", scan_id="hist1")
c = wf.plot(device_y="bpm4i", signal_y="bpm4i", scan_id="hist1")
assert c.config.current_x_mode == mode
@@ -1174,7 +1174,7 @@ def test_history_curve_x_modes_post_plot(qtbot, mocked_client, scan_history_fact
"""
wf = create_widget(qtbot, Waveform, client=mocked_client)
hist1, hist2 = inject_scan_history(wf, scan_history_factory, ("hist1", 1), ("hist2", 2))
c = wf.plot(y_name="bpm4i", y_entry="bpm4i", scan_id="hist1")
c = wf.plot(device_y="bpm4i", signal_y="bpm4i", scan_id="hist1")
# Change x_mode after plotting
wf.x_mode = mode
# Refresh history curves
@@ -1191,7 +1191,7 @@ def test_history_curve_incompatible_x_mode_hides_curve(qtbot, mocked_client, sca
# Inject history scan for this test
[history_msg] = inject_scan_history(wf, scan_history_factory, ("hist_bad", 1))
# Plot history curve
c = wf.plot(y_name="bpm4i", y_entry="bpm4i", scan_id=history_msg.scan_id)
c = wf.plot(device_y="bpm4i", signal_y="bpm4i", scan_id=history_msg.scan_id)
# Curve should be hidden due to incompatible x_mode
assert not c.isVisible()
@@ -1212,7 +1212,7 @@ def test_fetch_history_data_no_stored_data_raises(
# Force get_history_scan_item to return our dummy
monkeypatch.setattr(wf, "get_history_scan_item", lambda scan_id, scan_index: dummy_scan)
# Attempt to plot history curve should be suppressed by SafeSlot and return None
c = wf.plot(y_name="bpm4i", y_entry="bpm4i", scan_id="dummy", scan_number=1)
c = wf.plot(device_y="bpm4i", signal_y="bpm4i", scan_id="dummy", scan_number=1)
assert c is None
assert len(wf.curves) == 0
@@ -1224,7 +1224,7 @@ def test_history_curve_device_missing_returns_none(qtbot, mocked_client, scan_hi
wf = create_widget(qtbot, Waveform, client=mocked_client)
wf.x_mode = "index"
[history_msg] = inject_scan_history(wf, scan_history_factory, ("hist_dev_missing", 1))
c = wf.plot(y_name="non-existing", y_entry="non-existing", scan_id=history_msg.scan_id)
c = wf.plot(device_y="non-existing", signal_y="non-existing", scan_id=history_msg.scan_id)
assert c is None
@@ -1238,7 +1238,7 @@ def test_history_curve_custom_shape_mismatch_hides_curve(
wf.x_mode = "async_device"
[history_msg] = inject_scan_history(wf, scan_history_factory, ("hist_custom_shape", 1))
# Force shape mismatch for x-data
c = wf.plot(y_name="bpm4i", y_entry="bpm4i", scan_id=history_msg.scan_id)
c = wf.plot(device_y="bpm4i", signal_y="bpm4i", scan_id=history_msg.scan_id)
assert c is not None
assert not c.isVisible()
@@ -1250,7 +1250,7 @@ def test_history_curve_index_mode_plots_curve(qtbot, mocked_client, scan_history
wf = create_widget(qtbot, Waveform, client=mocked_client)
wf.x_mode = "index"
[history_msg] = inject_scan_history(wf, scan_history_factory, ("hist_index", 1))
c = wf.plot(y_name="bpm4i", y_entry="bpm4i", scan_id=history_msg.scan_id)
c = wf.plot(device_y="bpm4i", signal_y="bpm4i", scan_id=history_msg.scan_id)
assert c is not None
assert c.isVisible()
assert c.config.current_x_mode == "index"
@@ -1263,7 +1263,7 @@ def test_history_curve_timestamp_mode_plots_curve(qtbot, mocked_client, scan_his
wf = create_widget(qtbot, Waveform, client=mocked_client)
wf.x_mode = "timestamp"
[history_msg] = inject_scan_history(wf, scan_history_factory, ("hist_time", 1))
c = wf.plot(y_name="bpm4i", y_entry="bpm4i", scan_id=history_msg.scan_id)
c = wf.plot(device_y="bpm4i", signal_y="bpm4i", scan_id=history_msg.scan_id)
assert c is not None
assert c.isVisible()
assert c.config.current_x_mode == "timestamp"
@@ -1279,7 +1279,7 @@ def test_history_curve_auto_valid_uses_first_report_device(
wf.x_mode = "auto"
[history_msg] = inject_scan_history(wf, scan_history_factory, ("hist_auto_valid", 1))
# Plot history curve
c = wf.plot(y_name="bpm4i", y_entry="bpm4i", scan_id=history_msg.scan_id)
c = wf.plot(device_y="bpm4i", signal_y="bpm4i", scan_id=history_msg.scan_id)
assert c is not None
assert c.isVisible()
# Should have fallen back to the first scan_report_device
@@ -1295,7 +1295,7 @@ def test_history_curve_file_not_found_returns_none(qtbot, mocked_client, scan_hi
# Inject a valid history message then corrupt its file_path
[history_msg] = inject_scan_history(wf, scan_history_factory, ("bad_file", 1))
history_msg.file_path = "/nonexistent/path.h5"
c = wf.plot(y_name="bpm4i", y_entry="bpm4i", scan_id=history_msg.scan_id)
c = wf.plot(device_y="bpm4i", signal_y="bpm4i", scan_id=history_msg.scan_id)
assert c is None
@@ -1306,5 +1306,5 @@ def test_history_curve_scan_not_found_returns_none(qtbot, mocked_client):
wf = create_widget(qtbot, Waveform, client=mocked_client)
wf.x_mode = "index"
# No history scans injected for this widget
c = wf.plot(y_name="bpm4i", y_entry="bpm4i", scan_id="unknown_scan")
c = wf.plot(device_y="bpm4i", signal_y="bpm4i", scan_id="unknown_scan")
assert c is None