bec-widgets 0.49.0__py3-none-any.whl → 0.49.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.
@@ -1,4 +1,3 @@
1
- from .editor import BECEditor
2
1
  from .figure import BECFigure, FigureConfig
3
2
  from .monitor import BECMonitor
4
3
  from .motor_control import (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bec-widgets
3
- Version: 0.49.0
3
+ Version: 0.49.1
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
@@ -12,8 +12,8 @@ Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
13
  Requires-Dist: pydantic
14
14
  Requires-Dist: qtconsole
15
- Requires-Dist: PyQt6 >=6.0
16
- Requires-Dist: PyQt6-QScintilla
15
+ Requires-Dist: PyQt6-Qt6 <=6.6.3
16
+ Requires-Dist: PyQt6 <=6.6.3
17
17
  Requires-Dist: jedi
18
18
  Requires-Dist: qtpy
19
19
  Requires-Dist: pyqtgraph
@@ -33,7 +33,7 @@ Requires-Dist: isort ; extra == 'dev'
33
33
  Provides-Extra: pyqt5
34
34
  Requires-Dist: PyQt5 >=5.9 ; extra == 'pyqt5'
35
35
  Provides-Extra: pyqt6
36
- Requires-Dist: PyQt6 >=6.0 ; extra == 'pyqt6'
36
+ Requires-Dist: PyQt6 <=6.6.3 ; extra == 'pyqt6'
37
37
 
38
38
  # BEC Widgets
39
39
 
@@ -43,9 +43,7 @@ bec_widgets/utils/widget_io.py,sha256=JKl508VnqQSxcaHqKaoBQ1TWSOm3pXhxQGx7iF_pRA
43
43
  bec_widgets/utils/yaml_dialog.py,sha256=soZI8BOjlqYGfYDga70MEvkxJTsktq4y7B3uog2cSik,1851
44
44
  bec_widgets/validation/__init__.py,sha256=ismd1bU5FhFb0zFPwNKuq7oT48G4Y2GfaMZOdNKUtGk,132
45
45
  bec_widgets/validation/monitor_config_validator.py,sha256=M9p8K_nvxicnqJB4X7j90R377WHYVH4wMCtSXsRI51M,8150
46
- bec_widgets/widgets/__init__.py,sha256=GptryTiWJ4yWZZVBG_03guISJabSOzVpOMRkgW0LdTg,383
47
- bec_widgets/widgets/editor/__init__.py,sha256=5mBdFYi_IpygCz81kbLEZUWhd1b6oqiO3nASejuV_ug,30
48
- bec_widgets/widgets/editor/editor.py,sha256=pIIYLPqqqhXqT11Xj10cyGEiy-ieNGE4ZujN5lf0e68,15110
46
+ bec_widgets/widgets/__init__.py,sha256=HBzIWJYX4dp2iDZl_qIuyy-X5IWRMhGwQ-4UisP8wqE,353
49
47
  bec_widgets/widgets/figure/__init__.py,sha256=3hGx_KOV7QHCYAV06aNuUgKq4QIYCjUTad-DrwkUaBM,44
50
48
  bec_widgets/widgets/figure/figure.py,sha256=55Dc3DwdeC4rBDz9KLF6udfQJjnDuLQ-1QJ5oOF4Quw,28559
51
49
  bec_widgets/widgets/monitor/__init__.py,sha256=afXuZcBOxNAuYdCkIQXX5J60R5A3Q_86lNEW2vpFtPI,32
@@ -82,7 +80,6 @@ tests/unit_tests/test_bec_motor_map.py,sha256=IXSfitUGxOPqmngwVNPK5nwi2QDcXWjBkG
82
80
  tests/unit_tests/test_client_utils.py,sha256=fIApd5WgnJuyIzV-hdSABn6T-aOel2Wr2xuUX4Z651A,774
83
81
  tests/unit_tests/test_config_dialog.py,sha256=5uNGcpvrx8qDdMwFCTXr8HMzFZF4rFi-ZHoDpMxGMf8,6955
84
82
  tests/unit_tests/test_crosshair.py,sha256=d7fX-ymboZPALNqqiAj86PZ96llmGZ_3jf0yjVP0S94,5039
85
- tests/unit_tests/test_editor.py,sha256=TED5k1xFJHRZ4KDAg2VxSRu_hMJnra-lbAmVwsDicsM,6784
86
83
  tests/unit_tests/test_eiger_plot.py,sha256=bWnKBQid0YcLMQeBLy6ojb4ZpwTG-rFVT0kMg9Y08p8,4427
87
84
  tests/unit_tests/test_generate_cli_client.py,sha256=BdpTZMNUFOBJa2e-rme9AJUoXfueYyLiUCOpGi3SNvc,2400
88
85
  tests/unit_tests/test_motor_control.py,sha256=jdTG35z3jOL9XCAIDNIGfdv60vcwGLHa3KJjKqJkoZw,20322
@@ -96,8 +93,8 @@ tests/unit_tests/test_widget_io.py,sha256=FeL3ZYSBQnRt6jxj8VGYw1cmcicRQyHKleahw7
96
93
  tests/unit_tests/test_yaml_dialog.py,sha256=HNrqferkdg02-9ieOhhI2mr2Qvt7GrYgXmQ061YCTbg,5794
97
94
  tests/unit_tests/test_msgs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
95
  tests/unit_tests/test_msgs/available_scans_message.py,sha256=m_z97hIrjHXXMa2Ex-UvsPmTxOYXfjxyJaGkIY6StTY,46532
99
- bec_widgets-0.49.0.dist-info/LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
100
- bec_widgets-0.49.0.dist-info/METADATA,sha256=wbmutwjuhuPbCr157vS6FI5qbT7iJPf7VOzcCWt25cA,3714
101
- bec_widgets-0.49.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
102
- bec_widgets-0.49.0.dist-info/top_level.txt,sha256=EXCwhJYmXmd1DjYYL3hrGsddX-97IwYSiIHrf27FFVk,18
103
- bec_widgets-0.49.0.dist-info/RECORD,,
96
+ bec_widgets-0.49.1.dist-info/LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
97
+ bec_widgets-0.49.1.dist-info/METADATA,sha256=zNfCnStMfaGxDZGKjyISa0HkT057GKdMEwVWKIjONGU,3719
98
+ bec_widgets-0.49.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
99
+ bec_widgets-0.49.1.dist-info/top_level.txt,sha256=EXCwhJYmXmd1DjYYL3hrGsddX-97IwYSiIHrf27FFVk,18
100
+ bec_widgets-0.49.1.dist-info/RECORD,,
@@ -1 +0,0 @@
1
- from .editor import BECEditor
@@ -1,407 +0,0 @@
1
- import subprocess
2
-
3
- import qdarktheme
4
- from jedi import Script
5
- from jedi.api import Completion
6
- from qtconsole.manager import QtKernelManager
7
- from qtconsole.rich_jupyter_widget import RichJupyterWidget
8
-
9
- # pylint: disable=no-name-in-module
10
- from qtpy.Qsci import QsciAPIs, QsciLexerPython, QsciScintilla
11
- from qtpy.QtCore import Qt, QThread, Signal
12
- from qtpy.QtGui import QColor, QFont
13
- from qtpy.QtWidgets import QApplication, QFileDialog, QSplitter, QTextEdit, QVBoxLayout, QWidget
14
-
15
- from bec_widgets.widgets.toolbar import ModularToolBar
16
-
17
-
18
- class AutoCompleter(QThread):
19
- """Initializes the AutoCompleter thread for handling autocompletion and signature help.
20
-
21
- Args:
22
- file_path (str): The path to the file for which autocompletion is required.
23
- api (QsciAPIs): The QScintilla API instance used for managing autocompletions.
24
- enable_docstring (bool, optional): Flag to determine if docstrings should be included in the signatures.
25
- """
26
-
27
- def __init__(self, file_path: str, api: QsciAPIs, enable_docstring: bool = False):
28
- super().__init__(None)
29
- self.file_path = file_path
30
- self.script: Script = None
31
- self.api: QsciAPIs = api
32
- self.completions: list[Completion] = None
33
- self.line = 0
34
- self.index = 0
35
- self.text = ""
36
-
37
- # TODO so far disabled, quite buggy, docstring extraction has to be generalised
38
- self.enable_docstring = enable_docstring
39
-
40
- def update_script(self, text: str):
41
- """Updates the script for Jedi completion based on the current editor text.
42
-
43
- Args:
44
- text (str): The current text of the editor.
45
- """
46
- if self.script is None or self.script.path != text:
47
- self.script = Script(text, path=self.file_path)
48
-
49
- def run(self):
50
- """Runs the thread for generating autocompletions. Overrides QThread.run."""
51
- self.update_script(self.text)
52
- try:
53
- self.completions = self.script.complete(self.line, self.index)
54
- self.load_autocomplete(self.completions)
55
- except Exception as err:
56
- print(err)
57
- self.finished.emit()
58
-
59
- def get_function_signature(self, line: int, index: int, text: str) -> str:
60
- """Fetches the function signature for a given position in the text.
61
-
62
- Args:
63
- line (int): The line number in the editor.
64
- index (int): The index (column number) in the line.
65
- text (str): The current text of the editor.
66
-
67
- Returns:
68
- str: A string containing the function signature or an empty string if not available.
69
- """
70
- self.update_script(text)
71
- try:
72
- signatures = self.script.get_signatures(line, index)
73
- if signatures and self.enable_docstring is True:
74
- full_docstring = signatures[0].docstring(raw=True)
75
- compact_docstring = self.get_compact_docstring(full_docstring)
76
- return compact_docstring
77
- if signatures and self.enable_docstring is False:
78
- return signatures[0].to_string()
79
- except Exception as err:
80
- print(f"Signature Error:{err}")
81
- return ""
82
-
83
- def load_autocomplete(self, completions: list):
84
- """Loads the autocomplete suggestions into the QScintilla API.
85
-
86
- Args:
87
- completions (list[Completion]): A list of Completion objects to be added to the API.
88
- """
89
- self.api.clear()
90
- for i in completions:
91
- self.api.add(i.name)
92
- self.api.prepare()
93
-
94
- def get_completions(self, line: int, index: int, text: str):
95
- """Starts the autocompletion process for a given position in the text.
96
-
97
- Args:
98
- line (int): The line number in the editor.
99
- index (int): The index (column number) in the line.
100
- text (str): The current text of the editor.
101
- """
102
- self.line = line
103
- self.index = index
104
- self.text = text
105
- self.start()
106
-
107
- def get_compact_docstring(self, full_docstring):
108
- """Generates a compact version of a function's docstring.
109
-
110
- Args:
111
- full_docstring (str): The full docstring of a function.
112
-
113
- Returns:
114
- str: A compact version of the docstring.
115
- """
116
- lines = full_docstring.split("\n")
117
- # TODO make it also for different docstring styles, now it is only for numpy style
118
- cutoff_indices = [
119
- i
120
- for i, line in enumerate(lines)
121
- if line.strip().lower() in ["parameters", "returns", "examples", "see also", "warnings"]
122
- ]
123
-
124
- if cutoff_indices:
125
- lines = lines[: cutoff_indices[0]]
126
-
127
- compact_docstring = "\n".join(lines).strip()
128
- return compact_docstring
129
-
130
-
131
- class ScriptRunnerThread(QThread):
132
- """Initializes the thread for running a Python script.
133
-
134
- Args:
135
- script (str): The script to be executed.
136
- """
137
-
138
- outputSignal = Signal(str)
139
-
140
- def __init__(self, script):
141
- super().__init__()
142
- self.script = script
143
-
144
- def run(self):
145
- """Executes the script in a subprocess and emits output through a signal. Overrides QThread.run."""
146
- process = subprocess.Popen(
147
- ["python", "-u", "-c", self.script],
148
- stdout=subprocess.PIPE,
149
- stderr=subprocess.PIPE,
150
- bufsize=1,
151
- universal_newlines=True,
152
- text=True,
153
- )
154
-
155
- while True:
156
- output = process.stdout.readline()
157
- if output == "" and process.poll() is not None:
158
- break
159
- if output:
160
- self.outputSignal.emit(output)
161
- error = process.communicate()[1]
162
- if error:
163
- self.outputSignal.emit(error)
164
-
165
-
166
- class BECEditor(QWidget):
167
- """Initializes the BEC Editor widget.
168
-
169
- Args:
170
- toolbar_enabled (bool, optional): Determines if the toolbar should be enabled. Defaults to True.
171
- """
172
-
173
- def __init__(
174
- self, toolbar_enabled=True, jupyter_terminal_enabled=False, docstring_tooltip=False
175
- ):
176
- super().__init__()
177
-
178
- self.script_runner_thread = None
179
- self.file_path = None
180
- self.docstring_tooltip = docstring_tooltip
181
- self.jupyter_terminal_enabled = jupyter_terminal_enabled
182
- # TODO just temporary solution, could be extended to other languages
183
- self.is_python_file = True
184
-
185
- # Initialize the editor and terminal
186
- self.editor = QsciScintilla()
187
- if self.jupyter_terminal_enabled:
188
- self.terminal = self.make_jupyter_widget_with_kernel()
189
- else:
190
- self.terminal = QTextEdit()
191
- self.terminal.setReadOnly(True)
192
-
193
- # Layout
194
- self.layout = QVBoxLayout()
195
-
196
- # Initialize and add the toolbar if enabled
197
- if toolbar_enabled:
198
- self.toolbar = ModularToolBar(self)
199
- self.layout.addWidget(self.toolbar)
200
-
201
- # Initialize the splitter
202
- self.splitter = QSplitter(Qt.Orientation.Vertical, self)
203
- self.splitter.addWidget(self.editor)
204
- self.splitter.addWidget(self.terminal)
205
- self.splitter.setSizes([400, 200])
206
-
207
- # Add Splitter to layout
208
- self.layout.addWidget(self.splitter)
209
- self.setLayout(self.layout)
210
-
211
- self.setup_editor()
212
-
213
- def setup_editor(self):
214
- """Sets up the editor with necessary configurations like lexer, auto indentation, and line numbers."""
215
- # Set the lexer for Python
216
- self.lexer = QsciLexerPython()
217
- self.editor.setLexer(self.lexer)
218
-
219
- # Enable auto indentation and competition within the editor
220
- self.editor.setAutoIndent(True)
221
- self.editor.setIndentationsUseTabs(False)
222
- self.editor.setIndentationWidth(4)
223
- self.editor.setAutoCompletionSource(QsciScintilla.AutoCompletionSource.AcsAll)
224
- self.editor.setAutoCompletionThreshold(1)
225
-
226
- # Autocomplete for python file
227
- # Connect cursor position change signal for autocompletion
228
- self.editor.cursorPositionChanged.connect(self.on_cursor_position_changed)
229
-
230
- # if self.is_python_file: #TODO can be changed depending on supported languages
231
- self.__api = QsciAPIs(self.lexer)
232
- self.auto_completer = AutoCompleter(
233
- self.editor.text(), self.__api, enable_docstring=self.docstring_tooltip
234
- )
235
- self.auto_completer.finished.connect(self.loaded_autocomplete)
236
-
237
- # Enable line numbers in the margin
238
- self.editor.setMarginType(0, QsciScintilla.MarginType.NumberMargin)
239
- self.editor.setMarginWidth(0, "0000") # Adjust the width as needed
240
-
241
- # Additional UI elements like menu for load/save can be added here
242
- self.set_editor_style()
243
-
244
- @staticmethod
245
- def make_jupyter_widget_with_kernel() -> object:
246
- """Start a kernel, connect to it, and create a RichJupyterWidget to use it"""
247
- kernel_manager = QtKernelManager(kernel_name="python3")
248
- kernel_manager.start_kernel()
249
-
250
- kernel_client = kernel_manager.client()
251
- kernel_client.start_channels()
252
-
253
- jupyter_widget = RichJupyterWidget()
254
- jupyter_widget.set_default_style("linux")
255
- jupyter_widget.kernel_manager = kernel_manager
256
- jupyter_widget.kernel_client = kernel_client
257
- return jupyter_widget
258
-
259
- def show_call_tip(self, position):
260
- """Shows a call tip at the given position in the editor.
261
-
262
- Args:
263
- position (int): The position in the editor where the call tip should be shown.
264
- """
265
- line, index = self.editor.lineIndexFromPosition(position)
266
- signature = self.auto_completer.get_function_signature(line + 1, index, self.editor.text())
267
- if signature:
268
- self.editor.showUserList(1, [signature])
269
-
270
- def on_cursor_position_changed(self, line, index):
271
- """Handles the event of cursor position change in the editor.
272
-
273
- Args:
274
- line (int): The current line number where the cursor is.
275
- index (int): The current column index where the cursor is.
276
- """
277
- # if self.is_python_file: #TODO can be changed depending on supported languages
278
- # Get completions
279
- self.auto_completer.get_completions(line + 1, index, self.editor.text())
280
- self.editor.autoCompleteFromAPIs()
281
-
282
- # Show call tip - signature
283
- position = self.editor.positionFromLineIndex(line, index)
284
- self.show_call_tip(position)
285
-
286
- def loaded_autocomplete(self):
287
- """Placeholder method for actions after autocompletion data is loaded."""
288
-
289
- def set_editor_style(self):
290
- """Sets the style and color scheme for the editor."""
291
- # Dracula Theme Colors
292
- background_color = QColor("#282a36")
293
- text_color = QColor("#f8f8f2")
294
- keyword_color = QColor("#8be9fd")
295
- string_color = QColor("#f1fa8c")
296
- comment_color = QColor("#6272a4")
297
- class_function_color = QColor("#50fa7b")
298
-
299
- # Set Font
300
- font = QFont()
301
- font.setFamily("Consolas")
302
- font.setPointSize(10)
303
- self.editor.setFont(font)
304
- self.editor.setMarginsFont(font)
305
-
306
- # Set Editor Colors
307
- self.editor.setMarginsBackgroundColor(background_color)
308
- self.editor.setMarginsForegroundColor(text_color)
309
- self.editor.setCaretForegroundColor(text_color)
310
- self.editor.setCaretLineBackgroundColor(QColor("#44475a"))
311
- self.editor.setPaper(background_color) # Set the background color for the entire paper
312
- self.editor.setColor(text_color)
313
-
314
- # Set editor
315
- # Syntax Highlighting Colors
316
- lexer = self.editor.lexer()
317
- if lexer:
318
- lexer.setDefaultPaper(background_color) # Set the background color for the text area
319
- lexer.setDefaultColor(text_color)
320
- lexer.setColor(keyword_color, QsciLexerPython.Keyword)
321
- lexer.setColor(string_color, QsciLexerPython.DoubleQuotedString)
322
- lexer.setColor(string_color, QsciLexerPython.SingleQuotedString)
323
- lexer.setColor(comment_color, QsciLexerPython.Comment)
324
- lexer.setColor(class_function_color, QsciLexerPython.ClassName)
325
- lexer.setColor(class_function_color, QsciLexerPython.FunctionMethodName)
326
-
327
- # Set the style for all text to have a transparent background
328
- # TODO find better way how to do it!
329
- for style in range(
330
- 128
331
- ): # QsciScintilla supports 128 styles by default, this set all to transparent background
332
- self.lexer.setPaper(background_color, style)
333
-
334
- def run_script(self):
335
- """Runs the current script in the editor."""
336
- if self.jupyter_terminal_enabled:
337
- script = self.editor.text()
338
- self.terminal.execute(script)
339
-
340
- else:
341
- script = self.editor.text()
342
- self.script_runner_thread = ScriptRunnerThread(script)
343
- self.script_runner_thread.outputSignal.connect(self.update_terminal)
344
- self.script_runner_thread.start()
345
-
346
- def update_terminal(self, text):
347
- """Updates the terminal with new text.
348
-
349
- Args:
350
- text (str): The text to be appended to the terminal.
351
- """
352
- self.terminal.append(text)
353
-
354
- def enable_docstring_tooltip(self):
355
- """Enables the docstring tooltip."""
356
- self.docstring_tooltip = True
357
- self.auto_completer.enable_docstring = True
358
-
359
- def open_file(self):
360
- """Opens a file dialog for selecting and opening a Python file in the editor."""
361
- options = QFileDialog.Options()
362
- options |= QFileDialog.DontUseNativeDialog
363
- file_path, _ = QFileDialog.getOpenFileName(
364
- self, "Open file", "", "Python files (*.py);;All Files (*)", options=options
365
- )
366
-
367
- if not file_path:
368
- return
369
- try:
370
- with open(file_path, "r") as file:
371
- text = file.read()
372
- self.editor.setText(text)
373
- except FileNotFoundError:
374
- print(f"The file {file_path} was not found.")
375
- except Exception as e:
376
- print(f"An error occurred while opening the file {file_path}: {e}")
377
-
378
- def save_file(self):
379
- """Opens a save file dialog for saving the current script in the editor."""
380
- options = QFileDialog.Options()
381
- options |= QFileDialog.DontUseNativeDialog
382
- file_path, _ = QFileDialog.getSaveFileName(
383
- self, "Save file", "", "Python files (*.py);;All Files (*)", options=options
384
- )
385
-
386
- if not file_path:
387
- return
388
- try:
389
- if not file_path.endswith(".py"):
390
- file_path += ".py"
391
-
392
- with open(file_path, "w") as file:
393
- text = self.editor.text()
394
- file.write(text)
395
- print(f"File saved to {file_path}")
396
- except Exception as e:
397
- print(f"An error occurred while saving the file to {file_path}: {e}")
398
-
399
-
400
- if __name__ == "__main__": # pragma: no cover
401
- app = QApplication([])
402
- qdarktheme.setup_theme("auto")
403
-
404
- mainWin = BECEditor(jupyter_terminal_enabled=True)
405
-
406
- mainWin.show()
407
- app.exec()
@@ -1,170 +0,0 @@
1
- # pylint: disable = no-name-in-module,missing-class-docstring, missing-module-docstring
2
-
3
- import os
4
- import tempfile
5
- from unittest.mock import MagicMock, mock_open, patch
6
-
7
- import pytest
8
- from qtpy.Qsci import QsciScintilla
9
- from qtpy.QtWidgets import QTextEdit
10
-
11
- from bec_widgets.widgets.editor.editor import AutoCompleter, BECEditor
12
-
13
-
14
- @pytest.fixture(scope="function")
15
- def editor(qtbot, docstring_tooltip=False):
16
- """Helper function to set up the BECEditor widget."""
17
- widget = BECEditor(toolbar_enabled=True, docstring_tooltip=docstring_tooltip)
18
- qtbot.addWidget(widget)
19
- qtbot.waitExposed(widget)
20
- yield widget
21
-
22
-
23
- def find_action_by_text(toolbar, text):
24
- """Helper function to find an action in the toolbar by its text."""
25
- for action in toolbar.actions():
26
- if action.text() == text:
27
- return action
28
- return None
29
-
30
-
31
- def test_bec_editor_initialization(editor):
32
- """Test if the BECEditor widget is initialized correctly."""
33
- assert isinstance(editor.editor, QsciScintilla)
34
- assert isinstance(editor.terminal, QTextEdit)
35
- assert isinstance(editor.auto_completer, AutoCompleter)
36
-
37
-
38
- @patch("bec_widgets.widgets.editor.editor.Script") # Mock the Script class from jedi
39
- def test_autocompleter_suggestions(mock_script, editor, qtbot):
40
- """Test if the autocompleter provides correct suggestions based on input."""
41
- # Set up mock return values for the Script.complete method
42
- mock_completion = MagicMock()
43
- mock_completion.name = "mocked_method"
44
- mock_script.return_value.complete.return_value = [mock_completion]
45
-
46
- # Simulate user input in the editor
47
- test_code = "print("
48
- editor.editor.setText(test_code)
49
- line, index = editor.editor.getCursorPosition()
50
-
51
- # Trigger autocomplete
52
- editor.auto_completer.get_completions(line, index, test_code)
53
-
54
- # Use qtbot to wait for the completion thread
55
- qtbot.waitUntil(lambda: editor.auto_completer.completions is not None, timeout=1000)
56
-
57
- # Check if the expected completion is in the autocompleter's suggestions
58
- suggested_methods = [completion.name for completion in editor.auto_completer.completions]
59
- assert "mocked_method" in suggested_methods
60
-
61
-
62
- @patch("bec_widgets.widgets.editor.editor.Script") # Mock the Script class from jedi
63
- @pytest.mark.parametrize(
64
- "docstring_enabled, expected_signature",
65
- [(True, "Mocked signature with docstring"), (False, "Mocked signature")],
66
- )
67
- def test_autocompleter_signature(mock_script, editor, docstring_enabled, expected_signature):
68
- """Test if the autocompleter provides correct function signature based on docstring setting."""
69
- # Set docstring mode based on parameter
70
- editor.docstring_tooltip = docstring_enabled
71
- editor.auto_completer.enable_docstring = docstring_enabled
72
-
73
- # Set up mock return values for the Script.get_signatures method
74
- mock_signature = MagicMock()
75
- if docstring_enabled:
76
- mock_signature.docstring.return_value = expected_signature
77
- else:
78
- mock_signature.to_string.return_value = expected_signature
79
- mock_script.return_value.get_signatures.return_value = [mock_signature]
80
-
81
- # Simulate user input that would trigger a signature request
82
- test_code = "print("
83
- editor.editor.setText(test_code)
84
- line, index = editor.editor.getCursorPosition()
85
-
86
- # Trigger signature request
87
- signature = editor.auto_completer.get_function_signature(line, index, test_code)
88
-
89
- # Check if the expected signature is returned
90
- assert signature == expected_signature
91
-
92
-
93
- def test_open_file(editor):
94
- """Test open_file method of BECEditor."""
95
- # Create a temporary file with some content
96
- with tempfile.NamedTemporaryFile(delete=False, suffix=".py") as temp_file:
97
- temp_file.write(b"test file content")
98
-
99
- # Mock user selecting the file in the dialog
100
- with patch("qtpy.QtWidgets.QFileDialog.getOpenFileName", return_value=(temp_file.name, "")):
101
- with patch("builtins.open", new_callable=mock_open, read_data="test file content"):
102
- editor.open_file()
103
-
104
- # Verify if the editor's text is set to the file content
105
- assert editor.editor.text() == "test file content"
106
-
107
- # Clean up by removing the temporary file
108
- os.remove(temp_file.name)
109
-
110
-
111
- def test_save_file(editor):
112
- """Test save_file method of BECEditor."""
113
- # Set some text in the editor
114
- editor.editor.setText("test save content")
115
-
116
- # Mock user selecting the file in the dialog
117
- with patch(
118
- "qtpy.QtWidgets.QFileDialog.getSaveFileName", return_value=("/path/to/save/file.py", "")
119
- ):
120
- with patch("builtins.open", new_callable=mock_open) as mock_file:
121
- editor.save_file()
122
-
123
- # Verify if the file was opened correctly for writing
124
- mock_file.assert_called_with("/path/to/save/file.py", "w")
125
-
126
- # Verify if the editor's text was written to the file
127
- mock_file().write.assert_called_with("test save content")
128
-
129
-
130
- def test_open_file_through_toolbar(editor):
131
- """Test the open_file method through the ModularToolBar."""
132
- # Create a temporary file
133
- with tempfile.NamedTemporaryFile(delete=False, suffix=".py") as temp_file:
134
- temp_file.write(b"test file content")
135
-
136
- # Find the open file action in the toolbar
137
- open_action = find_action_by_text(editor.toolbar, "Open File")
138
- assert open_action is not None, "Open File action should be found"
139
-
140
- # Mock the file dialog and built-in open function
141
- with patch("qtpy.QtWidgets.QFileDialog.getOpenFileName", return_value=(temp_file.name, "")):
142
- with patch("builtins.open", new_callable=mock_open, read_data="test file content"):
143
- open_action.trigger()
144
- # Verify if the editor's text is set to the file content
145
- assert editor.editor.text() == "test file content"
146
-
147
- # Clean up
148
- os.remove(temp_file.name)
149
-
150
-
151
- def test_save_file_through_toolbar(editor):
152
- """Test the save_file method through the ModularToolBar."""
153
- # Set some text in the editor
154
- editor.editor.setText("test save content")
155
-
156
- # Find the save file action in the toolbar
157
- save_action = find_action_by_text(editor.toolbar, "Save File")
158
- assert save_action is not None, "Save File action should be found"
159
-
160
- # Mock the file dialog and built-in open function
161
- with patch(
162
- "qtpy.QtWidgets.QFileDialog.getSaveFileName", return_value=("/path/to/save/file.py", "")
163
- ):
164
- with patch("builtins.open", new_callable=mock_open) as mock_file:
165
- save_action.trigger()
166
- # Verify if the file was opened correctly for writing
167
- mock_file.assert_called_with("/path/to/save/file.py", "w")
168
-
169
- # Verify if the editor's text was written to the file
170
- mock_file().write.assert_called_with("test save content")