bec-widgets 0.70.0__py3-none-any.whl → 0.71.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,131 +2,288 @@
2
2
  from unittest.mock import MagicMock
3
3
 
4
4
  import pytest
5
- from qtpy.QtWidgets import QLineEdit
5
+ from bec_lib.messages import AvailableResourceMessage
6
6
 
7
7
  from bec_widgets.utils.widget_io import WidgetIO
8
8
  from bec_widgets.widgets.scan_control import ScanControl
9
- from tests.unit_tests.test_msgs.available_scans_message import available_scans_message
10
9
 
11
-
12
- class FakePositioner:
13
- """Fake minimal positioner class for testing."""
14
-
15
- def __init__(self, name, enabled=True):
16
- self.name = name
17
- self.enabled = enabled
18
-
19
- def __contains__(self, item):
20
- return item == self.name
21
-
22
-
23
- def get_mocked_device(device_name):
24
- """Helper function to mock the devices"""
25
- if device_name == "samx":
26
- return FakePositioner(name="samx", enabled=True)
27
-
28
-
29
- @pytest.fixture(scope="function")
30
- def mocked_client():
31
- # Create a MagicMock object
32
- client = MagicMock()
33
-
34
- # Mock the producer.get method to return the packed message
35
- client.producer.get.return_value = available_scans_message
36
-
37
- # # Mock the device_manager.devices attribute to return a mock object for samx
38
- client.device_manager.devices = MagicMock()
39
- client.device_manager.devices.__contains__.side_effect = lambda x: x == "samx"
40
- client.device_manager.devices.samx = get_mocked_device("samx")
41
-
42
- return client
10
+ from .client_mocks import mocked_client
11
+
12
+ available_scans_message = AvailableResourceMessage(
13
+ resource={
14
+ "line_scan": {
15
+ "class": "LineScan",
16
+ "base_class": "ScanBase",
17
+ "arg_input": {"device": "device", "start": "float", "stop": "float"},
18
+ "gui_config": {
19
+ "scan_class_name": "LineScan",
20
+ "arg_group": {
21
+ "name": "Scan Arguments",
22
+ "bundle": 3,
23
+ "arg_inputs": {"device": "device", "start": "float", "stop": "float"},
24
+ "inputs": [
25
+ {
26
+ "arg": True,
27
+ "name": "device",
28
+ "type": "device",
29
+ "display_name": "Device",
30
+ "tooltip": None,
31
+ "default": None,
32
+ "expert": False,
33
+ },
34
+ {
35
+ "arg": True,
36
+ "name": "start",
37
+ "type": "float",
38
+ "display_name": "Start",
39
+ "tooltip": None,
40
+ "default": None,
41
+ "expert": False,
42
+ },
43
+ {
44
+ "arg": True,
45
+ "name": "stop",
46
+ "type": "float",
47
+ "display_name": "Stop",
48
+ "tooltip": None,
49
+ "default": None,
50
+ "expert": False,
51
+ },
52
+ ],
53
+ "min": 1,
54
+ "max": None,
55
+ },
56
+ "kwarg_groups": [
57
+ {
58
+ "name": "Movement Parameters",
59
+ "inputs": [
60
+ {
61
+ "arg": False,
62
+ "name": "steps",
63
+ "type": "int",
64
+ "display_name": "Steps",
65
+ "tooltip": "Number of steps",
66
+ "default": None,
67
+ "expert": False,
68
+ },
69
+ {
70
+ "arg": False,
71
+ "name": "relative",
72
+ "type": "bool",
73
+ "display_name": "Relative",
74
+ "tooltip": "If True, the start and end positions are relative to the current position",
75
+ "default": False,
76
+ "expert": False,
77
+ },
78
+ ],
79
+ },
80
+ {
81
+ "name": "Acquisition Parameters",
82
+ "inputs": [
83
+ {
84
+ "arg": False,
85
+ "name": "exp_time",
86
+ "type": "float",
87
+ "display_name": "Exp Time",
88
+ "tooltip": "Exposure time in s",
89
+ "default": 0,
90
+ "expert": False,
91
+ },
92
+ {
93
+ "arg": False,
94
+ "name": "burst_at_each_point",
95
+ "type": "int",
96
+ "display_name": "Burst At Each Point",
97
+ "tooltip": "Number of acquisition per point",
98
+ "default": 1,
99
+ "expert": False,
100
+ },
101
+ ],
102
+ },
103
+ ],
104
+ },
105
+ "required_kwargs": ["steps", "relative"],
106
+ "arg_bundle_size": {"bundle": 3, "min": 1, "max": None},
107
+ },
108
+ "grid_scan": {
109
+ "class": "Scan",
110
+ "base_class": "ScanBase",
111
+ "arg_input": {"device": "device", "start": "float", "stop": "float", "steps": "int"},
112
+ "gui_config": {
113
+ "scan_class_name": "Scan",
114
+ "arg_group": {
115
+ "name": "Scan Arguments",
116
+ "bundle": 4,
117
+ "arg_inputs": {
118
+ "device": "device",
119
+ "start": "float",
120
+ "stop": "float",
121
+ "steps": "int",
122
+ },
123
+ "inputs": [
124
+ {
125
+ "arg": True,
126
+ "name": "device",
127
+ "type": "device",
128
+ "display_name": "Device",
129
+ "tooltip": None,
130
+ "default": None,
131
+ "expert": False,
132
+ },
133
+ {
134
+ "arg": True,
135
+ "name": "start",
136
+ "type": "float",
137
+ "display_name": "Start",
138
+ "tooltip": None,
139
+ "default": None,
140
+ "expert": False,
141
+ },
142
+ {
143
+ "arg": True,
144
+ "name": "stop",
145
+ "type": "float",
146
+ "display_name": "Stop",
147
+ "tooltip": None,
148
+ "default": None,
149
+ "expert": False,
150
+ },
151
+ {
152
+ "arg": True,
153
+ "name": "steps",
154
+ "type": "int",
155
+ "display_name": "Steps",
156
+ "tooltip": None,
157
+ "default": None,
158
+ "expert": False,
159
+ },
160
+ ],
161
+ "min": 2,
162
+ "max": None,
163
+ },
164
+ "kwarg_groups": [
165
+ {
166
+ "name": "Scan Parameters",
167
+ "inputs": [
168
+ {
169
+ "arg": False,
170
+ "name": "exp_time",
171
+ "type": "float",
172
+ "display_name": "Exp Time",
173
+ "tooltip": "Exposure time in seconds",
174
+ "default": 0,
175
+ "expert": False,
176
+ },
177
+ {
178
+ "arg": False,
179
+ "name": "settling_time",
180
+ "type": "float",
181
+ "display_name": "Settling Time",
182
+ "tooltip": "Settling time in seconds",
183
+ "default": 0,
184
+ "expert": False,
185
+ },
186
+ {
187
+ "arg": False,
188
+ "name": "burst_at_each_point",
189
+ "type": "int",
190
+ "display_name": "Burst At Each Point",
191
+ "tooltip": "Number of exposures at each point",
192
+ "default": 1,
193
+ "expert": False,
194
+ },
195
+ {
196
+ "arg": False,
197
+ "name": "relative",
198
+ "type": "bool",
199
+ "display_name": "Relative",
200
+ "tooltip": "If True, the motors will be moved relative to their current position",
201
+ "default": False,
202
+ "expert": False,
203
+ },
204
+ ],
205
+ }
206
+ ],
207
+ },
208
+ "required_kwargs": ["relative"],
209
+ "arg_bundle_size": {"bundle": 4, "min": 2, "max": None},
210
+ },
211
+ "not_supported_scan_class": {"base_class": "NotSupportedScanClass"},
212
+ }
213
+ )
43
214
 
44
215
 
45
216
  @pytest.fixture(scope="function")
46
217
  def scan_control(qtbot, mocked_client): # , mock_dev):
218
+ mocked_client.connector.set("scans/available_scans", available_scans_message)
47
219
  widget = ScanControl(client=mocked_client)
48
- # widget.dev.samx = MagicMock()
49
220
  qtbot.addWidget(widget)
50
221
  qtbot.waitExposed(widget)
51
222
  yield widget
52
223
 
53
224
 
54
225
  def test_populate_scans(scan_control, mocked_client):
55
- # The comboBox should be populated with all scan from the message right after initialization
56
- expected_scans = available_scans_message.resource.keys()
57
- assert scan_control.comboBox_scan_selection.count() == len(expected_scans)
58
- for scan in expected_scans: # Each scan should be in the comboBox
59
- assert scan_control.comboBox_scan_selection.findText(scan) != -1
226
+ expected_scans = ["line_scan", "grid_scan"]
227
+ items = [
228
+ scan_control.comboBox_scan_selection.itemText(i)
229
+ for i in range(scan_control.comboBox_scan_selection.count())
230
+ ]
231
+
232
+ assert scan_control.comboBox_scan_selection.count() == 2
233
+ assert sorted(items) == sorted(expected_scans)
60
234
 
61
235
 
62
- @pytest.mark.parametrize(
63
- "scan_name", ["line_scan", "grid_scan"]
64
- ) # TODO now only for line_scan and grid_scan, later for all loaded scans
236
+ @pytest.mark.parametrize("scan_name", ["line_scan", "grid_scan"])
65
237
  def test_on_scan_selected(scan_control, scan_name):
66
- # Expected scan info from the message signature
67
238
  expected_scan_info = available_scans_message.resource[scan_name]
68
-
69
- # Select a scan from the comboBox
70
239
  scan_control.comboBox_scan_selection.setCurrentText(scan_name)
71
240
 
72
- # Check labels and widgets in args table
241
+ # Check arg_box labels and widgets
73
242
  for index, (arg_key, arg_value) in enumerate(expected_scan_info["arg_input"].items()):
74
- label = scan_control.args_table.horizontalHeaderItem(index)
75
- assert label.text().lower() == arg_key # labes
243
+ label = scan_control.arg_box.layout.itemAtPosition(0, index).widget()
244
+ assert label.text().lower() == arg_key
76
245
 
77
- for row in range(expected_scan_info["arg_bundle_size"]["min"]):
78
- widget = scan_control.args_table.cellWidget(row, index)
246
+ for row in range(1, expected_scan_info["arg_bundle_size"]["min"] + 1):
247
+ widget = scan_control.arg_box.layout.itemAtPosition(row, index).widget()
79
248
  assert widget is not None # Confirm that a widget exists
80
- expected_widget_type = scan_control.WIDGET_HANDLER.get(arg_value, None)
249
+ expected_widget_type = scan_control.arg_box.WIDGET_HANDLER.get(arg_value, None)
81
250
  assert isinstance(widget, expected_widget_type) # Confirm the widget type matches
82
251
 
83
- # kwargs
84
- kwargs_from_signature = [
85
- param for param in expected_scan_info["signature"] if param["kind"] == "KEYWORD_ONLY"
86
- ]
252
+ # Check kwargs boxes
253
+ kwargs_group = [param for param in expected_scan_info["gui_config"]["kwarg_groups"]]
254
+ print(kwargs_group)
87
255
 
88
- # Check labels and widgets in kwargs grid layout
89
- for index, kwarg_info in enumerate(kwargs_from_signature):
90
- label_widget = scan_control.kwargs_layout.itemAtPosition(1, index).widget()
91
- assert label_widget.text() == kwarg_info["name"].capitalize()
92
- widget = scan_control.kwargs_layout.itemAtPosition(2, index).widget()
93
- expected_widget_type = scan_control.WIDGET_HANDLER.get(kwarg_info["annotation"], QLineEdit)
94
- assert isinstance(widget, expected_widget_type)
256
+ for kwarg_box, kwarg_group in zip(scan_control.kwarg_boxes, kwargs_group):
257
+ assert kwarg_box.title() == kwarg_group["name"]
258
+ for index, kwarg_info in enumerate(kwarg_group["inputs"]):
259
+ label = kwarg_box.layout.itemAtPosition(0, index).widget()
260
+ assert label.text() == kwarg_info["display_name"]
261
+ widget = kwarg_box.layout.itemAtPosition(1, index).widget()
262
+ expected_widget_type = kwarg_box.WIDGET_HANDLER.get(kwarg_info["type"], None)
263
+ assert isinstance(widget, expected_widget_type)
95
264
 
96
265
 
97
266
  @pytest.mark.parametrize("scan_name", ["line_scan", "grid_scan"])
98
- def test_add_remove_bundle(scan_control, scan_name):
99
- # Expected scan info from the message signature
267
+ def test_add_remove_bundle(scan_control, scan_name, qtbot):
100
268
  expected_scan_info = available_scans_message.resource[scan_name]
101
-
102
- # Select a scan from the comboBox
103
269
  scan_control.comboBox_scan_selection.setCurrentText(scan_name)
104
270
 
105
271
  # Initial number of args row
106
- initial_num_of_rows = scan_control.args_table.rowCount()
107
-
108
- # Check initial row count of args table
109
- assert scan_control.args_table.rowCount() == expected_scan_info["arg_bundle_size"]["min"]
272
+ initial_num_of_rows = scan_control.arg_box.count_arg_rows()
110
273
 
111
- # Try to remove default number of args row
112
- scan_control.pushButton_remove_bundle.click()
113
- assert scan_control.args_table.rowCount() == expected_scan_info["arg_bundle_size"]["min"]
274
+ assert initial_num_of_rows == expected_scan_info["arg_bundle_size"]["min"]
114
275
 
115
- # Try to add two bundles
116
- scan_control.pushButton_add_bundle.click()
117
- scan_control.pushButton_add_bundle.click()
276
+ scan_control.button_add_bundle.click()
277
+ scan_control.button_add_bundle.click()
118
278
 
119
- # check the case where no max number of args are defined
120
- # TODO do check also for the case where max number of args are defined
121
279
  if expected_scan_info["arg_bundle_size"]["max"] is None:
122
- assert scan_control.args_table.rowCount() == initial_num_of_rows + 2
280
+ assert scan_control.arg_box.count_arg_rows() == initial_num_of_rows + 2
123
281
 
124
282
  # Remove one bundle
125
- scan_control.pushButton_remove_bundle.click()
283
+ scan_control.button_remove_bundle.click()
284
+ qtbot.wait(200)
126
285
 
127
- # check the case where no max number of args are defined
128
- if expected_scan_info["arg_bundle_size"]["max"] is None:
129
- assert scan_control.args_table.rowCount() == initial_num_of_rows + 1
286
+ assert scan_control.arg_box.count_arg_rows() == initial_num_of_rows + 1
130
287
 
131
288
 
132
289
  def test_run_line_scan_with_parameters(scan_control, mocked_client):
@@ -134,32 +291,21 @@ def test_run_line_scan_with_parameters(scan_control, mocked_client):
134
291
  kwargs = {"exp_time": 0.1, "steps": 10, "relative": True, "burst_at_each_point": 1}
135
292
  args = {"device": "samx", "start": -5, "stop": 5}
136
293
 
137
- # Select a scan from the comboBox
138
294
  scan_control.comboBox_scan_selection.setCurrentText(scan_name)
139
295
 
140
296
  # Set kwargs in the UI
141
- for label_index in range(
142
- scan_control.kwargs_layout.rowCount() + 1
143
- ): # from some reason rowCount() returns 1 less than the actual number of rows
144
- label_item = scan_control.kwargs_layout.itemAtPosition(1, label_index)
145
- if label_item:
146
- label_widget = label_item.widget()
147
- kwarg_key = WidgetIO.get_value(label_widget).lower()
148
- if kwarg_key in kwargs:
149
- widget_item = scan_control.kwargs_layout.itemAtPosition(2, label_index)
150
- if widget_item:
151
- widget = widget_item.widget()
152
- WidgetIO.set_value(widget, kwargs[kwarg_key])
153
-
297
+ for kwarg_box in scan_control.kwarg_boxes:
298
+ for widget in kwarg_box.widgets:
299
+ for key, value in kwargs.items():
300
+ if widget.arg_name == key:
301
+ WidgetIO.set_value(widget, value)
302
+ break
154
303
  # Set args in the UI
155
- for col_index in range(scan_control.args_table.columnCount()):
156
- header_item = scan_control.args_table.horizontalHeaderItem(col_index)
157
- if header_item:
158
- arg_key = header_item.text().lower()
159
- if arg_key in args:
160
- for row_index in range(scan_control.args_table.rowCount()):
161
- widget = scan_control.args_table.cellWidget(row_index, col_index)
162
- WidgetIO.set_value(widget, args[arg_key])
304
+ for widget in scan_control.arg_box.widgets:
305
+ for key, value in args.items():
306
+ if widget.arg_name == key:
307
+ WidgetIO.set_value(widget, value)
308
+ break
163
309
 
164
310
  # Mock the scan function
165
311
  mocked_scan_function = MagicMock()
@@ -172,13 +318,7 @@ def test_run_line_scan_with_parameters(scan_control, mocked_client):
172
318
  called_args, called_kwargs = mocked_scan_function.call_args
173
319
 
174
320
  # Check if the scan function was called correctly
175
- expected_device = (
176
- mocked_client.device_manager.devices.samx
177
- ) # This is the FakePositioner instance
321
+ expected_device = mocked_client.device_manager.devices.samx
178
322
  expected_args_list = [expected_device, args["start"], args["stop"]]
179
- assert called_args == tuple(
180
- expected_args_list
181
- ), "The positional arguments passed to the scan function do not match expected values."
182
- assert (
183
- called_kwargs == kwargs
184
- ), "The keyword arguments passed to the scan function do not match expected values."
323
+ assert called_args == tuple(expected_args_list)
324
+ assert called_kwargs == kwargs
@@ -0,0 +1,160 @@
1
+ # pylint: disable = no-name-in-module,missing-class-docstring, missing-module-docstring
2
+ import pytest
3
+
4
+ from bec_widgets.utils.widget_io import WidgetIO
5
+ from bec_widgets.widgets.scan_control.scan_group_box import ScanGroupBox
6
+
7
+
8
+ def test_kwarg_box(qtbot):
9
+ group_input = {
10
+ "name": "Kwarg Test",
11
+ "inputs": [
12
+ # Test float
13
+ {
14
+ "arg": False,
15
+ "name": "exp_time",
16
+ "type": "float",
17
+ "display_name": "Exp Time",
18
+ "tooltip": "Exposure time in seconds",
19
+ "default": 0,
20
+ "expert": False,
21
+ },
22
+ # Test int
23
+ {
24
+ "arg": False,
25
+ "name": "num_points",
26
+ "type": "int",
27
+ "display_name": "Num Points",
28
+ "tooltip": "Number of points",
29
+ "default": 1,
30
+ "expert": False,
31
+ },
32
+ # Test bool
33
+ {
34
+ "arg": False,
35
+ "name": "relative",
36
+ "type": "bool",
37
+ "display_name": "Relative",
38
+ "tooltip": "If True, the motors will be moved relative to their current position",
39
+ "default": False,
40
+ "expert": False,
41
+ },
42
+ # Test str
43
+ {
44
+ "arg": False,
45
+ "name": "scan_type",
46
+ "type": "str",
47
+ "display_name": "Scan Type",
48
+ "tooltip": "Type of scan",
49
+ "default": "line",
50
+ "expert": False,
51
+ },
52
+ ],
53
+ }
54
+
55
+ kwarg_box = ScanGroupBox(box_type="kwargs", config=group_input)
56
+ assert kwarg_box is not None
57
+ assert kwarg_box.box_type == "kwargs"
58
+ assert kwarg_box.config == group_input
59
+ assert kwarg_box.title() == "Kwarg Test"
60
+
61
+ # Labels
62
+ assert kwarg_box.layout.itemAtPosition(0, 0).widget().text() == "Exp Time"
63
+ assert kwarg_box.layout.itemAtPosition(0, 1).widget().text() == "Num Points"
64
+ assert kwarg_box.layout.itemAtPosition(0, 2).widget().text() == "Relative"
65
+ assert kwarg_box.layout.itemAtPosition(0, 3).widget().text() == "Scan Type"
66
+
67
+ # Widget 0
68
+ assert kwarg_box.widgets[0].__class__.__name__ == "ScanDoubleSpinBox"
69
+ assert kwarg_box.widgets[0].arg_name == "exp_time"
70
+ assert WidgetIO.get_value(kwarg_box.widgets[0]) == 0
71
+ assert kwarg_box.widgets[0].toolTip() == "Exposure time in seconds"
72
+
73
+ # Widget 1
74
+ assert kwarg_box.widgets[1].__class__.__name__ == "ScanSpinBox"
75
+ assert kwarg_box.widgets[1].arg_name == "num_points"
76
+ assert WidgetIO.get_value(kwarg_box.widgets[1]) == 1
77
+ assert kwarg_box.widgets[1].toolTip() == "Number of points"
78
+
79
+ # Widget 2
80
+ assert kwarg_box.widgets[2].__class__.__name__ == "ScanCheckBox"
81
+ assert kwarg_box.widgets[2].arg_name == "relative"
82
+ assert WidgetIO.get_value(kwarg_box.widgets[2]) == False
83
+ assert (
84
+ kwarg_box.widgets[2].toolTip()
85
+ == "If True, the motors will be moved relative to their current position"
86
+ )
87
+
88
+ # Widget 3
89
+ assert kwarg_box.widgets[3].__class__.__name__ == "ScanLineEdit"
90
+ assert kwarg_box.widgets[3].arg_name == "scan_type"
91
+ assert WidgetIO.get_value(kwarg_box.widgets[3]) == "line"
92
+ assert kwarg_box.widgets[3].toolTip() == "Type of scan"
93
+
94
+ parameters = kwarg_box.get_parameters()
95
+ assert parameters == {"exp_time": 0, "num_points": 1, "relative": False, "scan_type": "line"}
96
+
97
+
98
+ def test_arg_box(qtbot):
99
+ group_input = {
100
+ "name": "Arg Test",
101
+ "inputs": [
102
+ # Test device
103
+ {
104
+ "arg": True,
105
+ "name": "device",
106
+ "type": "str",
107
+ "display_name": "Device",
108
+ "tooltip": "Device to scan",
109
+ "default": "samx",
110
+ "expert": False,
111
+ },
112
+ # Test float
113
+ {
114
+ "arg": True,
115
+ "name": "start",
116
+ "type": "float",
117
+ "display_name": "Start",
118
+ "tooltip": "Start position",
119
+ "default": 0,
120
+ "expert": False,
121
+ },
122
+ # Test int
123
+ {
124
+ "arg": True,
125
+ "name": "stop",
126
+ "type": "int",
127
+ "display_name": "Stop",
128
+ "tooltip": "Stop position",
129
+ "default": 1,
130
+ "expert": False,
131
+ },
132
+ ],
133
+ }
134
+
135
+ arg_box = ScanGroupBox(box_type="args", config=group_input)
136
+ assert arg_box is not None
137
+ assert arg_box.box_type == "args"
138
+ assert arg_box.config == group_input
139
+ assert arg_box.title() == "Arg Test"
140
+
141
+ # Labels
142
+ assert arg_box.layout.itemAtPosition(0, 0).widget().text() == "Device"
143
+ assert arg_box.layout.itemAtPosition(0, 1).widget().text() == "Start"
144
+ assert arg_box.layout.itemAtPosition(0, 2).widget().text() == "Stop"
145
+
146
+ # Widget 0
147
+ assert arg_box.widgets[0].__class__.__name__ == "ScanLineEdit"
148
+ assert arg_box.widgets[0].arg_name == "device"
149
+ assert WidgetIO.get_value(arg_box.widgets[0]) == "samx"
150
+ assert arg_box.widgets[0].toolTip() == "Device to scan"
151
+
152
+ # Widget 1
153
+ assert arg_box.widgets[1].__class__.__name__ == "ScanDoubleSpinBox"
154
+ assert arg_box.widgets[1].arg_name == "start"
155
+ assert WidgetIO.get_value(arg_box.widgets[1]) == 0
156
+ assert arg_box.widgets[1].toolTip() == "Start position"
157
+
158
+ # Widget 2
159
+ assert arg_box.widgets[2].__class__.__name__ == "ScanSpinBox"
160
+ assert arg_box.widgets[2].arg_name