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

feat(bec-login): Add login widget in material design style

This commit is contained in:
2026-02-19 13:53:57 +01:00
committed by Christian Appel
parent 1e3661c318
commit c998e3ec48
2 changed files with 143 additions and 0 deletions

View File

@@ -0,0 +1,91 @@
"""
Login dialog for user authentication.
The Login Widget is styled in a Material Design style and emits
the entered credentials through a signal for further processing.
"""
from qtpy.QtCore import Qt, Signal
from qtpy.QtWidgets import QLabel, QLineEdit, QPushButton, QVBoxLayout, QWidget
class BECLogin(QWidget):
"""Login dialog for user authentication in Material Design style."""
credentials_entered = Signal(str, str)
def __init__(self, parent=None):
super().__init__(parent=parent)
# Only displayed if this widget as standalone widget, and not embedded in another widget
self.setWindowTitle("Login")
title = QLabel("Sign in", parent=self)
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
title.setStyleSheet(
"""
#QLabel
{
font-size: 18px;
font-weight: 600;
}
"""
)
self.username = QLineEdit(parent=self)
self.username.setPlaceholderText("Username")
self.password = QLineEdit(parent=self)
self.password.setPlaceholderText("Password")
self.password.setEchoMode(QLineEdit.EchoMode.Password)
self.ok_btn = QPushButton("Sign in", parent=self)
self.ok_btn.setDefault(True)
self.ok_btn.clicked.connect(self._emit_credentials)
# If the user presses Enter in the password field, trigger the OK button click
self.password.returnPressed.connect(self.ok_btn.click)
# Build Layout
layout = QVBoxLayout(self)
layout.setContentsMargins(32, 32, 32, 32)
layout.setSpacing(16)
layout.addWidget(title)
layout.addSpacing(8)
layout.addWidget(self.username)
layout.addWidget(self.password)
layout.addSpacing(12)
layout.addWidget(self.ok_btn)
self.username.setFocus()
self.setStyleSheet(
"""
QLineEdit {
padding: 8px;
}
"""
)
def _clear_password(self):
"""Clear the password field."""
self.password.clear()
def _emit_credentials(self):
"""Emit credentials and clear the password field."""
self.credentials_entered.emit(self.username.text().strip(), self.password.text())
self._clear_password()
if __name__ == "__main__": # pragma: no cover
import sys
from bec_qthemes import apply_theme
from qtpy.QtWidgets import QApplication
app = QApplication(sys.argv)
apply_theme("light")
dialog = BECLogin()
dialog.credentials_entered.connect(lambda u, p: print(f"Username: {u}, Password: {p}"))
dialog.show()
sys.exit(app.exec_())

View File

@@ -0,0 +1,52 @@
"""Test the BEC Login widget"""
import pytest
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QLineEdit
from bec_widgets.utils.bec_login import BECLogin
@pytest.fixture
def login_dialog(qtbot):
"""Fixture to create a BECLogin instance."""
dialog = BECLogin()
qtbot.addWidget(dialog)
qtbot.waitExposed(dialog) # Ensure the dialog is fully shown before running tests
return dialog
def test_utils_login_dialog_initialization(login_dialog, qtbot):
"""Test that the BECLogin initializes correctly."""
assert login_dialog.windowTitle() == "Login"
assert login_dialog.username.placeholderText() == "Username"
assert login_dialog.password.placeholderText() == "Password"
assert login_dialog.password.echoMode() == QLineEdit.EchoMode.Password
assert login_dialog.ok_btn.text() == "Sign in"
# Initially, this should be empty
with qtbot.waitSignal(login_dialog.credentials_entered, timeout=5000) as blocker:
qtbot.mouseClick(login_dialog.ok_btn, Qt.MouseButton.LeftButton)
assert blocker.args == ["", ""]
def test_utils_login_dialog_emit_credentials(login_dialog, qtbot):
"""Test that the BECLogin emits credentials correctly."""
test_username = "testuser "
test_password = "testpass"
login_dialog.username.setText(test_username)
login_dialog.password.setText(test_password)
with qtbot.waitSignal(login_dialog.credentials_entered, timeout=5000) as blocker:
qtbot.mouseClick(login_dialog.ok_btn, Qt.MouseButton.LeftButton)
assert blocker.args == [test_username.strip(), test_password]
assert login_dialog.password.text() == "" # Password should be cleared after emitting
login_dialog.password.setText(test_password)
with qtbot.waitSignal(login_dialog.credentials_entered, timeout=5000) as blocker:
qtbot.keyClick(login_dialog.password, Qt.Key.Key_Return)
assert blocker.args == [test_username.strip(), test_password]
assert login_dialog.password.text() == "" # Password should be cleared after emitting