mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-14 03:31:50 +02:00
283 lines
8.1 KiB
Python
283 lines
8.1 KiB
Python
import os
|
|
import tempfile
|
|
|
|
import pytest
|
|
from qtpy.QtCore import Property
|
|
from qtpy.QtWidgets import QCheckBox, QGroupBox, QLineEdit, QVBoxLayout, QWidget
|
|
|
|
from bec_widgets.utils.widget_state_manager import WidgetStateManager
|
|
|
|
|
|
class MyLineEdit(QLineEdit):
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
# Internal attribute to hold the color string
|
|
self._customColor = ""
|
|
|
|
@Property(str)
|
|
def customColor(self):
|
|
return self._customColor
|
|
|
|
@customColor.setter
|
|
def customColor(self, color):
|
|
self._customColor = color
|
|
|
|
|
|
# A specialized widget that has a property declared with stored=False
|
|
class MyLineEditStoredFalse(QLineEdit):
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self._noStoreProperty = ""
|
|
|
|
@Property(str, stored=False)
|
|
def noStoreProperty(self):
|
|
return self._noStoreProperty
|
|
|
|
@noStoreProperty.setter
|
|
def noStoreProperty(self, value):
|
|
self._noStoreProperty = value
|
|
|
|
|
|
@pytest.fixture
|
|
def test_widget(qtbot):
|
|
w = QWidget()
|
|
w.setObjectName("MainWidget")
|
|
layout = QVBoxLayout(w)
|
|
|
|
child1 = MyLineEdit(w)
|
|
child1.setObjectName("ChildLineEdit1")
|
|
child1.setText("Hello")
|
|
child1.customColor = "red"
|
|
|
|
child2 = MyLineEdit(w)
|
|
child2.setObjectName("ChildLineEdit2")
|
|
child2.setText("World")
|
|
child2.customColor = "blue"
|
|
|
|
# A widget that we want to skip settings
|
|
skip_widget = QCheckBox("Skip Widget", w)
|
|
skip_widget.setObjectName("SkipCheckBox")
|
|
skip_widget.setChecked(True)
|
|
skip_widget.setProperty("skip_settings", True)
|
|
|
|
layout.addWidget(child1)
|
|
layout.addWidget(child2)
|
|
layout.addWidget(skip_widget)
|
|
|
|
qtbot.addWidget(w)
|
|
qtbot.waitExposed(w)
|
|
return w
|
|
|
|
|
|
def test_save_load_widget_state(test_widget):
|
|
"""
|
|
Test saving and loading the state
|
|
"""
|
|
manager = WidgetStateManager(test_widget)
|
|
|
|
# Before saving, confirm initial properties
|
|
child1 = test_widget.findChild(MyLineEdit, "ChildLineEdit1")
|
|
child2 = test_widget.findChild(MyLineEdit, "ChildLineEdit2")
|
|
assert child1.text() == "Hello"
|
|
assert child1.customColor == "red"
|
|
assert child2.text() == "World"
|
|
assert child2.customColor == "blue"
|
|
|
|
# Create a temporary file to save settings
|
|
with tempfile.NamedTemporaryFile(delete=False, suffix=".ini") as tmp_file:
|
|
tmp_filename = tmp_file.name
|
|
|
|
# Save the current state
|
|
manager.save_state(tmp_filename)
|
|
|
|
# Modify the widget properties
|
|
child1.setText("Changed1")
|
|
child1.customColor = "green"
|
|
child2.setText("Changed2")
|
|
child2.customColor = "yellow"
|
|
|
|
assert child1.text() == "Changed1"
|
|
assert child1.customColor == "green"
|
|
assert child2.text() == "Changed2"
|
|
assert child2.customColor == "yellow"
|
|
|
|
# Load the previous state
|
|
manager.load_state(tmp_filename)
|
|
|
|
# Confirm that the state has been restored
|
|
assert child1.text() == "Hello"
|
|
assert child1.customColor == "red"
|
|
assert child2.text() == "World"
|
|
assert child2.customColor == "blue"
|
|
|
|
# Clean up temporary file
|
|
os.remove(tmp_filename)
|
|
|
|
|
|
def test_save_load_without_filename(test_widget, monkeypatch, qtbot):
|
|
"""
|
|
Test that the dialog would open if filename is not provided.
|
|
"""
|
|
manager = WidgetStateManager(test_widget)
|
|
|
|
# Mock QFileDialog.getSaveFileName to return a temporary filename
|
|
with tempfile.NamedTemporaryFile(delete=False, suffix=".ini") as tmp_file:
|
|
tmp_filename = tmp_file.name
|
|
|
|
def mock_getSaveFileName(*args, **kwargs):
|
|
return tmp_filename, "INI Files (*.ini)"
|
|
|
|
def mock_getOpenFileName(*args, **kwargs):
|
|
return tmp_filename, "INI Files (*.ini)"
|
|
|
|
from qtpy.QtWidgets import QFileDialog
|
|
|
|
monkeypatch.setattr(QFileDialog, "getSaveFileName", mock_getSaveFileName)
|
|
monkeypatch.setattr(QFileDialog, "getOpenFileName", mock_getOpenFileName)
|
|
|
|
# Initial values
|
|
child1 = test_widget.findChild(MyLineEdit, "ChildLineEdit1")
|
|
assert child1.text() == "Hello"
|
|
|
|
# Save state without providing filename -> uses dialog mock
|
|
manager.save_state()
|
|
|
|
# Change property
|
|
child1.setText("Modified")
|
|
|
|
# Load state using dialog mock
|
|
manager.load_state()
|
|
|
|
# State should be restored
|
|
assert child1.text() == "Hello"
|
|
|
|
# Clean up
|
|
os.remove(tmp_filename)
|
|
|
|
|
|
def test_skip_settings(test_widget):
|
|
"""
|
|
Verify that a widget with skip_settings=True is not saved/loaded.
|
|
"""
|
|
manager = WidgetStateManager(test_widget)
|
|
|
|
skip_checkbox = test_widget.findChild(QCheckBox, "SkipCheckBox")
|
|
# Double check initial state
|
|
assert skip_checkbox.isChecked() is True
|
|
|
|
with tempfile.NamedTemporaryFile(delete=False, suffix=".ini") as tmp_file:
|
|
tmp_filename = tmp_file.name
|
|
|
|
# Save state
|
|
manager.save_state(tmp_filename)
|
|
|
|
# Change skip checkbox state
|
|
skip_checkbox.setChecked(False)
|
|
assert skip_checkbox.isChecked() is False
|
|
|
|
# Load state
|
|
manager.load_state(tmp_filename)
|
|
|
|
# The skip checkbox should not revert because it was never saved.
|
|
assert skip_checkbox.isChecked() is False
|
|
|
|
os.remove(tmp_filename)
|
|
|
|
|
|
def test_property_stored_false(qtbot):
|
|
"""
|
|
Verify that a property with stored=False is not saved.
|
|
"""
|
|
w = QWidget()
|
|
w.setObjectName("TestStoredFalse")
|
|
layout = QVBoxLayout(w)
|
|
|
|
stored_false_widget = MyLineEditStoredFalse(w)
|
|
stored_false_widget.setObjectName("NoStoreLineEdit")
|
|
stored_false_widget.setText("VisibleText") # normal text property is stored
|
|
stored_false_widget.noStoreProperty = "ShouldNotBeStored"
|
|
layout.addWidget(stored_false_widget)
|
|
|
|
qtbot.addWidget(w)
|
|
qtbot.waitExposed(w)
|
|
|
|
manager = WidgetStateManager(w)
|
|
|
|
with tempfile.NamedTemporaryFile(delete=False, suffix=".ini") as tmp_file:
|
|
tmp_filename = tmp_file.name
|
|
|
|
# Save the current state
|
|
manager.save_state(tmp_filename)
|
|
|
|
# Modify the properties
|
|
stored_false_widget.setText("ChangedText")
|
|
stored_false_widget.noStoreProperty = "ChangedNoStore"
|
|
|
|
# Load the previous state
|
|
manager.load_state(tmp_filename)
|
|
|
|
# The text should have reverted
|
|
assert stored_false_widget.text() == "VisibleText"
|
|
# The noStoreProperty should remain changed, as it was never saved.
|
|
assert stored_false_widget.noStoreProperty == "ChangedNoStore"
|
|
|
|
os.remove(tmp_filename)
|
|
|
|
|
|
def test_skip_parent_settings(qtbot):
|
|
"""
|
|
Demonstrates that if a PARENT widget has skip_settings=True, all its
|
|
children (even if they do NOT have skip_settings=True) also get skipped.
|
|
"""
|
|
main_widget = QWidget()
|
|
main_widget.setObjectName("TopWidget")
|
|
layout = QVBoxLayout(main_widget)
|
|
|
|
# Create a parent widget with skip_settings=True
|
|
parent_group = QGroupBox("ParentGroup", main_widget)
|
|
parent_group.setObjectName("ParentGroupBox")
|
|
parent_group.setProperty("skip_settings", True) # The crucial setting
|
|
|
|
child_layout = QVBoxLayout(parent_group)
|
|
|
|
child_line_edit_1 = MyLineEdit(parent_group)
|
|
child_line_edit_1.setObjectName("ChildLineEditA")
|
|
child_line_edit_1.setText("OriginalA")
|
|
|
|
child_line_edit_2 = MyLineEdit(parent_group)
|
|
child_line_edit_2.setObjectName("ChildLineEditB")
|
|
child_line_edit_2.setText("OriginalB")
|
|
|
|
child_layout.addWidget(child_line_edit_1)
|
|
child_layout.addWidget(child_line_edit_2)
|
|
parent_group.setLayout(child_layout)
|
|
|
|
layout.addWidget(parent_group)
|
|
main_widget.setLayout(layout)
|
|
|
|
qtbot.addWidget(main_widget)
|
|
qtbot.waitExposed(main_widget)
|
|
|
|
manager = WidgetStateManager(main_widget)
|
|
|
|
# Create a temp file to hold settings
|
|
with tempfile.NamedTemporaryFile(delete=False, suffix=".ini") as tmp_file:
|
|
tmp_filename = tmp_file.name
|
|
|
|
# Save the state
|
|
manager.save_state(tmp_filename)
|
|
|
|
# Change child widget values
|
|
child_line_edit_1.setText("ChangedA")
|
|
child_line_edit_2.setText("ChangedB")
|
|
|
|
# Load state
|
|
manager.load_state(tmp_filename)
|
|
|
|
# Because the PARENT has skip_settings=True, none of its children get saved or loaded
|
|
# Hence, the changes remain and do NOT revert
|
|
assert child_line_edit_1.text() == "ChangedA"
|
|
assert child_line_edit_2.text() == "ChangedB"
|
|
|
|
os.remove(tmp_filename)
|