bec-widgets 0.44.5__py3-none-any.whl → 0.45.0__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.
@@ -15,7 +15,7 @@ from qtpy.QtCore import Slot as pyqtSlot
15
15
  from qtpy.QtWidgets import QWidget
16
16
 
17
17
  from bec_widgets.utils import BECConnector, Colors, ConnectionConfig, EntryValidator
18
- from bec_widgets.widgets.plots import BECPlotBase, WidgetConfig
18
+ from bec_widgets.widgets.plots.plot_base import BECPlotBase, WidgetConfig
19
19
 
20
20
 
21
21
  class SignalData(BaseModel):
@@ -25,13 +25,14 @@ class SignalData(BaseModel):
25
25
  entry: str
26
26
  unit: Optional[str] = None # todo implement later
27
27
  modifier: Optional[str] = None # todo implement later
28
+ limits: Optional[list[float]] = None # todo implement later
28
29
 
29
30
 
30
31
  class Signal(BaseModel):
31
32
  """The configuration of a signal in the 1D waveform widget."""
32
33
 
33
34
  source: str
34
- x: SignalData
35
+ x: SignalData # TODO maybe add metadata for config gui later
35
36
  y: SignalData
36
37
 
37
38
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bec-widgets
3
- Version: 0.44.5
3
+ Version: 0.45.0
4
4
  Summary: BEC Widgets
5
5
  Home-page: https://gitlab.psi.ch/bec/bec-widgets
6
6
  Project-URL: Bug Tracker, https://gitlab.psi.ch/bec/bec-widgets/issues
@@ -1,8 +1,8 @@
1
1
  bec_widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  bec_widgets/cli/__init__.py,sha256=yFyAmDteCcndbReunhnrfDib6JujQ7-BKWeVvuU0Ylw,30
3
- bec_widgets/cli/client.py,sha256=LHcqlz2PPt_b2BN5YGJLlMLcC1zHdNKRpj3F0pq8mus,34044
3
+ bec_widgets/cli/client.py,sha256=J3yvhteGky9XHXMoyN0am75uAE4qeo7Oj_dZbdtMHRM,37300
4
4
  bec_widgets/cli/client_utils.py,sha256=8tVOmAMo5x0tUywTfivvCt8n0tJoT5WzoCa3GtsZ-qE,9694
5
- bec_widgets/cli/generate_cli.py,sha256=s0huOR9DSoe69F4OemS4vj5ZQ_w9cCuFvPhuBmDJJ1g,3976
5
+ bec_widgets/cli/generate_cli.py,sha256=QqTacqaG3zYi-Zmun62j-4Tv-Az5rDbvFXA2ESFjWZ0,4010
6
6
  bec_widgets/cli/server.py,sha256=4552dbbdJlalIllQGJFR3AsN0iLl5Qs23BAeqQiof0s,4721
7
7
  bec_widgets/examples/__init__.py,sha256=WWQ0cu7m8sA4Ehy-DWdTIqSISjaHsbxhsNmNrMnhDZU,202
8
8
  bec_widgets/examples/eiger_plot/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -36,11 +36,11 @@ bec_widgets/utils/widget_io.py,sha256=JKl508VnqQSxcaHqKaoBQ1TWSOm3pXhxQGx7iF_pRA
36
36
  bec_widgets/utils/yaml_dialog.py,sha256=soZI8BOjlqYGfYDga70MEvkxJTsktq4y7B3uog2cSik,1851
37
37
  bec_widgets/validation/__init__.py,sha256=ismd1bU5FhFb0zFPwNKuq7oT48G4Y2GfaMZOdNKUtGk,132
38
38
  bec_widgets/validation/monitor_config_validator.py,sha256=M9p8K_nvxicnqJB4X7j90R377WHYVH4wMCtSXsRI51M,8150
39
- bec_widgets/widgets/__init__.py,sha256=UMySMLbB53R-ygLz7XOdfRsXxwhEzkGQ5fool3E1_Kc,437
39
+ bec_widgets/widgets/__init__.py,sha256=QNCuDcg0o3uvGbf74qXeFjEsgIzOeEcd1hoWabJ4tKQ,437
40
40
  bec_widgets/widgets/editor/__init__.py,sha256=5mBdFYi_IpygCz81kbLEZUWhd1b6oqiO3nASejuV_ug,30
41
41
  bec_widgets/widgets/editor/editor.py,sha256=pIIYLPqqqhXqT11Xj10cyGEiy-ieNGE4ZujN5lf0e68,15110
42
42
  bec_widgets/widgets/figure/__init__.py,sha256=3hGx_KOV7QHCYAV06aNuUgKq4QIYCjUTad-DrwkUaBM,44
43
- bec_widgets/widgets/figure/figure.py,sha256=iMsK2M4cGjLyj0hEGqJ1cHqFMAcRVu_kon6GOwwpHzk,29915
43
+ bec_widgets/widgets/figure/figure.py,sha256=-3SJu93gE73ttrfxD9NGPRmgvaZjH0jOX_7ovhrHXck,32454
44
44
  bec_widgets/widgets/figure/figure_debug_minimal.ui,sha256=GodXBvBvs5QAUsHbo3pcxR4o51Tvce4DTqpTluk3hOs,742
45
45
  bec_widgets/widgets/monitor/__init__.py,sha256=afXuZcBOxNAuYdCkIQXX5J60R5A3Q_86lNEW2vpFtPI,32
46
46
  bec_widgets/widgets/monitor/config_dialog.py,sha256=Z1a4WRIVlfEGdwC-QG25kba2EHCZWi5J843tBVZlWiI,20275
@@ -57,40 +57,42 @@ bec_widgets/widgets/motor_control/motor_control_selection.ui,sha256=vXXpvNWuL6xy
57
57
  bec_widgets/widgets/motor_control/motor_control_table.ui,sha256=t6aRKiSmutMfp0AyupavbCs0cal-FANEnlKQiPzC9PQ,2792
58
58
  bec_widgets/widgets/motor_map/__init__.py,sha256=K3c-3A_LbxK0UJ0_bV3opL-wGLTwBLendsJXsg8GAqE,32
59
59
  bec_widgets/widgets/motor_map/motor_map.py,sha256=A7VOs8RAiXaLZF4_oxOYJX5BwMhRvir0k0u5eEPFtLc,21909
60
- bec_widgets/widgets/plots/__init__.py,sha256=aS4zo2SSLK9pO6FdKrSC-dUigsQP3c50IJ81QPJfwBs,190
60
+ bec_widgets/widgets/plots/__init__.py,sha256=ncGc6re47T9Bq75RQrPrk2rX2V89QZWRFOwZjmvkQPw,241
61
61
  bec_widgets/widgets/plots/image.py,sha256=Ty-8etIYBQl1MJLcuwCtlLUcAuXbxBq4RLDikbTL34c,31748
62
- bec_widgets/widgets/plots/plot_base.py,sha256=aek2O0JNOt8mjHGJRCrKtF4jsbR55tpEHNb8sSrwrd4,8434
63
- bec_widgets/widgets/plots/waveform1d.py,sha256=AstOAq_zK2y1ej2HV-EpOh-w3e-RGOyNY0q9f2iWa-Q,25508
62
+ bec_widgets/widgets/plots/motor_map.py,sha256=yBqVvLgEQhiIpQyvmsoTUToEyLblVmS2sTST7N8hNQA,15115
63
+ bec_widgets/widgets/plots/plot_base.py,sha256=tF0oamgCVIXFrfD8jnMVTltkzYLLSQ0Dq9HAG57Ji3E,8446
64
+ bec_widgets/widgets/plots/waveform1d.py,sha256=RbI5dQlbAaC4A1WuQLS1DWpk5UjeELpaUEvHkFvZAbA,25631
64
65
  bec_widgets/widgets/scan_control/__init__.py,sha256=IOfHl15vxb_uC6KN62-PeUzbBha_vQyqkkXbJ2HU674,38
65
66
  bec_widgets/widgets/scan_control/scan_control.py,sha256=tbO9tbVynRvs4VCxTZ4ZFBDTVAojIr-zkl70vuHbWgw,17116
66
67
  bec_widgets/widgets/toolbar/__init__.py,sha256=d-TP4_cr_VbpwreMM4ePnfZ5YXsEPQ45ibEf75nuGoE,36
67
68
  bec_widgets/widgets/toolbar/toolbar.py,sha256=sxz7rbc8XNPS6n2WMObF4-2PqdYfPxVtsOZEGV6mqa0,5124
68
69
  tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
69
- tests/client_mocks.py,sha256=UUKX-c4p95GJ8x882FRhdkjO7aG6GtBnm3DbHbdCqoc,2105
70
+ tests/client_mocks.py,sha256=925yW3vFnS8h6ym_4kpgX299WGVeObkGneuiXNWRE7s,3596
70
71
  tests/conftest.py,sha256=wMFVnerrLZjpi-OnPf7Ugah_DCb51QD1WWcbR4e_M6k,1055
71
72
  tests/test_bec_connector.py,sha256=f2XXGGw3NoZLIUrDuZuEWwF_ttOYmmquCgUrV5XkIOY,1951
72
73
  tests/test_bec_dispatcher.py,sha256=vWQGdUyMvssrOyiVxdsOpErOh4awX_NSQWvKSBoRfAI,9223
73
- tests/test_bec_figure.py,sha256=9X9GOakh7pSzofAqloG4NCHhTc1Rc5bDENf5dYHLtsk,7070
74
- tests/test_bec_monitor.py,sha256=I3GOEHcxqbRsBA_cYO8MBCnZkkJF8MAkRJczSgjJBAo,8933
74
+ tests/test_bec_figure.py,sha256=-2KYedk32pCsXNmAN0OFUrQT101YV3rrSL0ET3KXjjA,7464
75
+ tests/test_bec_monitor.py,sha256=mN7gBY7oXY6j65zzihpy8r-FvwVoCQlie3F6SoVq0mo,7042
75
76
  tests/test_bec_monitor_scatter2D.py,sha256=UYs0pe9uBX7GpFVkhxw-Hx_MwTz7NKMtWIBqONTZ1JI,5797
76
- tests/test_config_dialog.py,sha256=V-4coDZi4DfXDHDsjG75Yd7VyBxweybiamWbMeUY4uo,8774
77
+ tests/test_bec_motor_map.py,sha256=5D-xPQQ731Q6wsEzAmqJqoy_FpHtlK1siwqql8sJ0p0,4613
78
+ tests/test_config_dialog.py,sha256=5uNGcpvrx8qDdMwFCTXr8HMzFZF4rFi-ZHoDpMxGMf8,6955
77
79
  tests/test_crosshair.py,sha256=d7fX-ymboZPALNqqiAj86PZ96llmGZ_3jf0yjVP0S94,5039
78
80
  tests/test_editor.py,sha256=TED5k1xFJHRZ4KDAg2VxSRu_hMJnra-lbAmVwsDicsM,6784
79
81
  tests/test_eiger_plot.py,sha256=bWnKBQid0YcLMQeBLy6ojb4ZpwTG-rFVT0kMg9Y08p8,4427
80
82
  tests/test_generate_cli_client.py,sha256=BdpTZMNUFOBJa2e-rme9AJUoXfueYyLiUCOpGi3SNvc,2400
81
- tests/test_motor_control.py,sha256=Ba6AQqc-rh-iezciy1qnqKTl65bZVod7UiSxri7Rxsc,22581
82
- tests/test_motor_map.py,sha256=rfu6i8Vd_O0xSzFDKNWtxoWQah6gImGwqTR6UPfaeVg,7446
83
+ tests/test_motor_control.py,sha256=jdTG35z3jOL9XCAIDNIGfdv60vcwGLHa3KJjKqJkoZw,20322
84
+ tests/test_motor_map.py,sha256=UEjmtIYI2mxq9BUeopqoqNNy7UiPJEts9h45ufsFcrA,5979
83
85
  tests/test_plot_base.py,sha256=bOdlgAxh9oKk5PwiQ_MSFmzr44uJ61Tlg242RCIhl5c,2610
84
86
  tests/test_scan_control.py,sha256=e6_YpyxcayK35jXBSfxinGNL_8G-jcrWap25eg3z4QI,7560
85
87
  tests/test_stream_plot.py,sha256=LNCYIj9CafremGaz-DwDktCRJRrjgfOdVewCUwwZE5s,5843
86
88
  tests/test_validator_errors.py,sha256=NFxyv0TIOXeZKZRRUBfVQ7bpunwY4KkG95yTUdQmvns,3532
87
- tests/test_waveform1d.py,sha256=FjJh6YkiT09NvZqW144Ar86oGLDHi6WYh1bhhBlj-6g,13135
89
+ tests/test_waveform1d.py,sha256=GzlQTAjbg35mMKhopLCiHy3fIx0ld6WTr46ZuMTeSTE,13644
88
90
  tests/test_widget_io.py,sha256=FeL3ZYSBQnRt6jxj8VGYw1cmcicRQyHKleahw7XIyR0,3475
89
91
  tests/test_yaml_dialog.py,sha256=HNrqferkdg02-9ieOhhI2mr2Qvt7GrYgXmQ061YCTbg,5794
90
92
  tests/test_msgs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
91
93
  tests/test_msgs/available_scans_message.py,sha256=m_z97hIrjHXXMa2Ex-UvsPmTxOYXfjxyJaGkIY6StTY,46532
92
- bec_widgets-0.44.5.dist-info/LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
93
- bec_widgets-0.44.5.dist-info/METADATA,sha256=I_tO6_2mn-R9i9Y_kR-I-9sSdOWgI9MRGYm981Bge4Q,3714
94
- bec_widgets-0.44.5.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
95
- bec_widgets-0.44.5.dist-info/top_level.txt,sha256=EXCwhJYmXmd1DjYYL3hrGsddX-97IwYSiIHrf27FFVk,18
96
- bec_widgets-0.44.5.dist-info/RECORD,,
94
+ bec_widgets-0.45.0.dist-info/LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
95
+ bec_widgets-0.45.0.dist-info/METADATA,sha256=9EPs7EAqxS-MNM5INN1K7kdqlawrd_oeyHwDhotp5wI,3714
96
+ bec_widgets-0.45.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
97
+ bec_widgets-0.45.0.dist-info/top_level.txt,sha256=EXCwhJYmXmd1DjYYL3hrGsddX-97IwYSiIHrf27FFVk,18
98
+ bec_widgets-0.45.0.dist-info/RECORD,,
tests/client_mocks.py CHANGED
@@ -1,8 +1,9 @@
1
1
  # pylint: disable = no-name-in-module,missing-class-docstring, missing-module-docstring
2
-
3
- from unittest.mock import MagicMock
2
+ from unittest.mock import MagicMock, patch
4
3
 
5
4
  import pytest
5
+ from bec_lib.device import Positioner
6
+ from bec_lib.devicemanager import DeviceContainer
6
7
 
7
8
 
8
9
  class FakeDevice:
@@ -12,7 +13,7 @@ class FakeDevice:
12
13
  self.name = name
13
14
  self.enabled = enabled
14
15
  self.signals = {self.name: {"value": 1.0}}
15
- self.description = {self.name: {"source": self.name}}
16
+ self.description = {self.name: {"source": self.name, "dtype": "number", "shape": []}}
16
17
 
17
18
  def __contains__(self, item):
18
19
  return item == self.name
@@ -38,41 +39,85 @@ class FakeDevice:
38
39
  return self.description
39
40
 
40
41
 
41
- def get_mocked_device(device_name: str):
42
- """
43
- Helper function to mock the devices
44
- Args:
45
- device_name(str): Name of the device to mock
46
- """
47
- return FakeDevice(name=device_name, enabled=True)
42
+ class FakePositioner(FakeDevice):
43
+ def __init__(self, name, enabled=True, limits=None, read_value=1.0):
44
+ super().__init__(name, enabled)
45
+ self.limits = limits if limits is not None else [0, 0]
46
+ self.read_value = read_value
47
+
48
+ def set_read_value(self, value):
49
+ self.read_value = value
50
+
51
+ def read(self):
52
+ return {self.name: {"value": self.read_value}}
53
+
54
+ def set_limits(self, limits):
55
+ self.limits = limits
56
+
57
+ def move(self, value, relative=False):
58
+ """Simulates moving the device to a new position."""
59
+ if relative:
60
+ self.read_value += value
61
+ else:
62
+ self.read_value = value
63
+ # Respect the limits
64
+ self.read_value = max(min(self.read_value, self.limits[1]), self.limits[0])
65
+
66
+ @property
67
+ def readback(self):
68
+ return MagicMock(get=MagicMock(return_value=self.read_value))
69
+
70
+
71
+ class DMMock:
72
+ def __init__(self):
73
+ self.devices = DeviceContainer()
74
+
75
+ def add_devives(self, devices: list):
76
+ for device in devices:
77
+ self.devices[device.name] = device
78
+
79
+
80
+ DEVICES = [
81
+ FakePositioner("samx", limits=[-10, 10], read_value=2.0),
82
+ FakePositioner("samy", limits=[-5, 5], read_value=3.0),
83
+ FakePositioner("aptrx", limits=None, read_value=4.0),
84
+ FakePositioner("aptry", limits=None, read_value=5.0),
85
+ FakeDevice("gauss_bpm"),
86
+ FakeDevice("gauss_adc1"),
87
+ FakeDevice("gauss_adc2"),
88
+ FakeDevice("gauss_adc3"),
89
+ FakeDevice("bpm4i"),
90
+ FakeDevice("bpm3a"),
91
+ FakeDevice("bpm3i"),
92
+ ]
48
93
 
49
94
 
50
95
  @pytest.fixture(scope="function")
51
96
  def mocked_client():
52
- # Create a dictionary of mocked devices
53
- device_names = [
54
- "samx",
55
- "samy",
56
- "gauss_bpm",
57
- "gauss_adc1",
58
- "gauss_adc2",
59
- "gauss_adc3",
60
- "bpm4i",
61
- "bpm3a",
62
- "bpm3i",
63
- ]
64
- mocked_devices = {name: get_mocked_device(name) for name in device_names}
65
-
66
97
  # Create a MagicMock object
67
98
  client = MagicMock()
68
99
 
69
100
  # Mock the device_manager.devices attribute
70
- client.device_manager.devices = MagicMock()
71
- client.device_manager.devices.__getitem__.side_effect = lambda x: mocked_devices.get(x)
72
- client.device_manager.devices.__contains__.side_effect = lambda x: x in mocked_devices
101
+ client.device_manager = DMMock()
102
+ client.device_manager.add_devives(DEVICES)
103
+
104
+ def mock_mv(*args, relative=False):
105
+ # Extracting motor and value pairs
106
+ for i in range(0, len(args), 2):
107
+ motor = args[i]
108
+ value = args[i + 1]
109
+ motor.move(value, relative=relative)
110
+ return MagicMock(wait=MagicMock())
111
+
112
+ client.scans = MagicMock(mv=mock_mv)
113
+
114
+ # Ensure isinstance check for Positioner passes
115
+ original_isinstance = isinstance
73
116
 
74
- # Set each device as an attribute of the mock
75
- for name, device in mocked_devices.items():
76
- setattr(client.device_manager.devices, name, device)
117
+ def isinstance_mock(obj, class_info):
118
+ if class_info == Positioner and isinstance(obj, FakePositioner):
119
+ return True
120
+ return original_isinstance(obj, class_info)
77
121
 
78
- return client
122
+ with patch("builtins.isinstance", new=isinstance_mock):
123
+ yield client
tests/test_bec_figure.py CHANGED
@@ -5,7 +5,8 @@ from unittest.mock import MagicMock
5
5
  import numpy as np
6
6
  import pytest
7
7
 
8
- from bec_widgets.widgets import BECFigure
8
+ from bec_widgets.widgets import BECFigure, BECMotorMap, BECWaveform1D
9
+ from bec_widgets.widgets.plots import BECImageShow
9
10
 
10
11
  from .client_mocks import mocked_client
11
12
 
@@ -66,6 +67,16 @@ def test_bec_figure_add_remove_plot(bec_figure):
66
67
  assert bec_figure._widgets["widget_2"].config.widget_class == "BECWaveform1D"
67
68
 
68
69
 
70
+ def test_add_different_types_of_widgets(bec_figure):
71
+ plt = bec_figure.plot("samx", "bpm4i")
72
+ im = bec_figure.image("eiger")
73
+ motor_map = bec_figure.motor_map("samx", "samy")
74
+
75
+ assert plt.__class__ == BECWaveform1D
76
+ assert im.__class__ == BECImageShow
77
+ assert motor_map.__class__ == BECMotorMap
78
+
79
+
69
80
  def test_access_widgets_access_errors(bec_figure):
70
81
  bec_figure.add_plot(row=0, col=0)
71
82
 
tests/test_bec_monitor.py CHANGED
@@ -7,6 +7,8 @@ import yaml
7
7
 
8
8
  from bec_widgets.widgets import BECMonitor
9
9
 
10
+ from .client_mocks import mocked_client
11
+
10
12
 
11
13
  def load_test_config(config_name):
12
14
  """Helper function to load config from yaml file."""
@@ -16,69 +18,6 @@ def load_test_config(config_name):
16
18
  return config
17
19
 
18
20
 
19
- class FakeDevice:
20
- """Fake minimal positioner class for testing."""
21
-
22
- def __init__(self, name, enabled=True):
23
- self.name = name
24
- self.enabled = enabled
25
- self.signals = {self.name: {"value": 1.0}}
26
- self.description = {self.name: {"source": self.name}}
27
-
28
- def __contains__(self, item):
29
- return item == self.name
30
-
31
- @property
32
- def _hints(self):
33
- return [self.name]
34
-
35
- def set_value(self, fake_value: float = 1.0) -> None:
36
- """
37
- Setup fake value for device readout
38
- Args:
39
- fake_value(float): Desired fake value
40
- """
41
- self.signals[self.name]["value"] = fake_value
42
-
43
- def describe(self) -> dict:
44
- """
45
- Get the description of the device
46
- Returns:
47
- dict: Description of the device
48
- """
49
- return self.description
50
-
51
-
52
- def get_mocked_device(device_name: str):
53
- """
54
- Helper function to mock the devices
55
- Args:
56
- device_name(str): Name of the device to mock
57
- """
58
- return FakeDevice(name=device_name, enabled=True)
59
-
60
-
61
- @pytest.fixture(scope="function")
62
- def mocked_client():
63
- # Create a dictionary of mocked devices
64
- device_names = ["samx", "gauss_bpm", "gauss_adc1", "gauss_adc2", "gauss_adc3", "bpm4i"]
65
- mocked_devices = {name: get_mocked_device(name) for name in device_names}
66
-
67
- # Create a MagicMock object
68
- client = MagicMock()
69
-
70
- # Mock the device_manager.devices attribute
71
- client.device_manager.devices = MagicMock()
72
- client.device_manager.devices.__getitem__.side_effect = lambda x: mocked_devices.get(x)
73
- client.device_manager.devices.__contains__.side_effect = lambda x: x in mocked_devices
74
-
75
- # Set each device as an attribute of the mock
76
- for name, device in mocked_devices.items():
77
- setattr(client.device_manager.devices, name, device)
78
-
79
- return client
80
-
81
-
82
21
  @pytest.fixture(scope="function")
83
22
  def monitor(bec_dispatcher, qtbot, mocked_client):
84
23
  # client = MagicMock()
@@ -266,9 +205,6 @@ def test_on_scan_segment(monitor, config_name, msg, metadata, expected_data):
266
205
  config = load_test_config(config_name)
267
206
  monitor.on_config_update(config)
268
207
 
269
- # Get hints
270
- monitor.dev.__getitem__.side_effect = mock_getitem
271
-
272
208
  # Mock scan_storage.find_scan_by_ID
273
209
  mock_scan_data = MagicMock()
274
210
  mock_scan_data.data = {
@@ -0,0 +1,125 @@
1
+ import pytest
2
+
3
+ from bec_widgets.widgets import BECMotorMap
4
+ from bec_widgets.widgets.plots.motor_map import MotorMapConfig
5
+ from bec_widgets.widgets.plots.waveform1d import Signal, SignalData
6
+
7
+ from .client_mocks import mocked_client
8
+
9
+
10
+ @pytest.fixture(scope="function")
11
+ def bec_motor_map(qtbot, mocked_client):
12
+ widget = BECMotorMap(client=mocked_client, gui_id="BECMotorMap_test")
13
+ # qtbot.addWidget(widget)
14
+ # qtbot.waitExposed(widget)
15
+ yield widget
16
+
17
+
18
+ def test_motor_map_init(bec_motor_map):
19
+ default_config = MotorMapConfig(widget_class="BECMotorMap", gui_id="BECMotorMap_test")
20
+
21
+ assert bec_motor_map.config == default_config
22
+
23
+
24
+ def test_motor_map_change_motors(bec_motor_map):
25
+ bec_motor_map.change_motors("samx", "samy")
26
+
27
+ assert bec_motor_map.config.signals.x == SignalData(name="samx", entry="samx", limits=[-10, 10])
28
+ assert bec_motor_map.config.signals.y == SignalData(name="samy", entry="samy", limits=[-5, 5])
29
+
30
+
31
+ def test_motor_map_get_limits(bec_motor_map):
32
+ expected_limits = {
33
+ "samx": [-10, 10],
34
+ "samy": [-5, 5],
35
+ }
36
+
37
+ for motor_name, expected_limit in expected_limits.items():
38
+ actual_limit = bec_motor_map._get_motor_limit(motor_name)
39
+ assert actual_limit == expected_limit
40
+
41
+
42
+ def test_motor_map_get_init_position(bec_motor_map):
43
+ bec_motor_map.set_precision(2)
44
+
45
+ motor_map_dev = bec_motor_map.client.device_manager.devices
46
+
47
+ expected_positions = {
48
+ ("samx", "samx"): motor_map_dev["samx"].read()["samx"]["value"],
49
+ ("samy", "samy"): motor_map_dev["samy"].read()["samy"]["value"],
50
+ ("aptrx", "aptrx"): motor_map_dev["aptrx"].read()["aptrx"]["value"],
51
+ ("aptry", "aptry"): motor_map_dev["aptry"].read()["aptry"]["value"],
52
+ }
53
+
54
+ for (motor_name, entry), expected_position in expected_positions.items():
55
+ actual_position = bec_motor_map._get_motor_init_position(motor_name, entry, 2)
56
+ assert actual_position == expected_position
57
+
58
+
59
+ def test_motor_movement_updates_position_and_database(bec_motor_map):
60
+ motor_map_dev = bec_motor_map.client.device_manager.devices
61
+
62
+ init_positions = {
63
+ "samx": [motor_map_dev["samx"].read()["samx"]["value"]],
64
+ "samy": [motor_map_dev["samy"].read()["samy"]["value"]],
65
+ }
66
+
67
+ bec_motor_map.change_motors("samx", "samy")
68
+
69
+ assert bec_motor_map.database_buffer["x"] == init_positions["samx"]
70
+ assert bec_motor_map.database_buffer["y"] == init_positions["samy"]
71
+
72
+ # Simulate motor movement for 'samx' only
73
+ new_position_samx = 4.0
74
+ bec_motor_map.on_device_readback({"signals": {"samx": {"value": new_position_samx}}})
75
+
76
+ init_positions["samx"].append(new_position_samx)
77
+ init_positions["samy"].append(init_positions["samy"][-1])
78
+ # Verify database update for 'samx'
79
+ assert bec_motor_map.database_buffer["x"] == init_positions["samx"]
80
+
81
+ # Verify 'samy' retains its last known position
82
+ assert bec_motor_map.database_buffer["y"] == init_positions["samy"]
83
+
84
+
85
+ def test_scatter_plot_rendering(bec_motor_map):
86
+ motor_map_dev = bec_motor_map.client.device_manager.devices
87
+
88
+ init_positions = {
89
+ "samx": [motor_map_dev["samx"].read()["samx"]["value"]],
90
+ "samy": [motor_map_dev["samy"].read()["samy"]["value"]],
91
+ }
92
+
93
+ bec_motor_map.change_motors("samx", "samy")
94
+
95
+ # Simulate motor movement for 'samx' only
96
+ new_position_samx = 4.0
97
+ bec_motor_map.on_device_readback({"signals": {"samx": {"value": new_position_samx}}})
98
+ bec_motor_map._update_plot()
99
+
100
+ # Get the scatter plot item
101
+ scatter_plot_item = bec_motor_map.plot_components["scatter"]
102
+
103
+ # Check the scatter plot item properties
104
+ assert len(scatter_plot_item.data) > 0, "Scatter plot data is empty"
105
+ x_data = scatter_plot_item.data["x"]
106
+ y_data = scatter_plot_item.data["y"]
107
+ assert x_data[-1] == new_position_samx, "Scatter plot X data not updated correctly"
108
+ assert (
109
+ y_data[-1] == init_positions["samy"][-1]
110
+ ), "Scatter plot Y data should retain last known position"
111
+
112
+
113
+ def test_plot_visualization_consistency(bec_motor_map):
114
+ bec_motor_map.change_motors("samx", "samy")
115
+ # Simulate updating the plot with new data
116
+ bec_motor_map.on_device_readback({"signals": {"samx": {"value": 5}}})
117
+ bec_motor_map.on_device_readback({"signals": {"samy": {"value": 9}}})
118
+ bec_motor_map._update_plot()
119
+
120
+ scatter_plot_item = bec_motor_map.plot_components["scatter"]
121
+
122
+ # Check if the scatter plot reflects the new data correctly
123
+ assert (
124
+ scatter_plot_item.data["x"][-1] == 5 and scatter_plot_item.data["y"][-1] == 9
125
+ ), "Plot not updated correctly with new data"
@@ -8,6 +8,8 @@ from qtpy.QtWidgets import QTableWidgetItem, QTabWidget
8
8
 
9
9
  from bec_widgets.widgets.monitor.config_dialog import ConfigDialog
10
10
 
11
+ from .client_mocks import mocked_client
12
+
11
13
 
12
14
  def load_test_config(config_name):
13
15
  """Helper function to load config from yaml file."""
@@ -17,69 +19,6 @@ def load_test_config(config_name):
17
19
  return config
18
20
 
19
21
 
20
- class FakeDevice:
21
- """Fake minimal positioner class for testing."""
22
-
23
- def __init__(self, name, enabled=True):
24
- self.name = name
25
- self.enabled = enabled
26
- self.signals = {self.name: {"value": 1.0}}
27
- self.description = {self.name: {"source": self.name}}
28
-
29
- def __contains__(self, item):
30
- return item == self.name
31
-
32
- @property
33
- def _hints(self):
34
- return [self.name]
35
-
36
- def set_value(self, fake_value: float = 1.0) -> None:
37
- """
38
- Setup fake value for device readout
39
- Args:
40
- fake_value(float): Desired fake value
41
- """
42
- self.signals[self.name]["value"] = fake_value
43
-
44
- def describe(self) -> dict:
45
- """
46
- Get the description of the device
47
- Returns:
48
- dict: Description of the device
49
- """
50
- return self.description
51
-
52
-
53
- def get_mocked_device(device_name: str):
54
- """
55
- Helper function to mock the devices
56
- Args:
57
- device_name(str): Name of the device to mock
58
- """
59
- return FakeDevice(name=device_name, enabled=True)
60
-
61
-
62
- @pytest.fixture(scope="function")
63
- def mocked_client():
64
- # Create a dictionary of mocked devices
65
- device_names = ["samx", "gauss_bpm", "gauss_adc1", "gauss_adc2", "gauss_adc3", "bpm4i"]
66
- mocked_devices = {name: get_mocked_device(name) for name in device_names}
67
-
68
- # Create a MagicMock object
69
- client = MagicMock()
70
-
71
- # Mock the device_manager.devices attribute
72
- client.device_manager.devices = MagicMock()
73
- client.device_manager.devices.__getitem__.side_effect = lambda x: mocked_devices.get(x)
74
- client.device_manager.devices.__contains__.side_effect = lambda x: x in mocked_devices
75
-
76
- # Set each device as an attribute of the mock
77
- for name, device in mocked_devices.items():
78
- setattr(client.device_manager.devices, name, device)
79
-
80
- return client
81
-
82
-
83
22
  @pytest.fixture(scope="function")
84
23
  def config_dialog(qtbot, mocked_client):
85
24
  client = mocked_client