mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-08 07:18:47 +00:00
Merge pull request #627 from PR0M3TH3AN/codex/implement-seedpass-gui-with-services
Implement GUI app skeleton with headless tests
This commit is contained in:
@@ -33,3 +33,5 @@ python-multipart
|
||||
orjson
|
||||
argon2-cffi
|
||||
toga-core>=0.5.2
|
||||
pillow
|
||||
toga-dummy>=0.5.2 # for headless GUI tests
|
||||
|
@@ -1,11 +1,11 @@
|
||||
"""Graphical user interface for SeedPass."""
|
||||
|
||||
from .app import SeedPassApp
|
||||
from .app import SeedPassApp, build
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Launch the GUI application."""
|
||||
SeedPassApp().main_loop()
|
||||
build().main_loop()
|
||||
|
||||
|
||||
__all__ = ["SeedPassApp", "main"]
|
||||
|
@@ -16,10 +16,12 @@ class LockScreenWindow(toga.Window):
|
||||
"""Window prompting for the master password."""
|
||||
|
||||
def __init__(
|
||||
self, app: SeedPassApp, vault: VaultService, entries: EntryService
|
||||
self, controller: SeedPassApp, vault: VaultService, entries: EntryService
|
||||
) -> None:
|
||||
super().__init__("Unlock Vault")
|
||||
self.app = app
|
||||
# Store a reference to the SeedPass application instance separately from
|
||||
# the ``toga`` ``Window.app`` attribute to avoid conflicts.
|
||||
self.controller = controller
|
||||
self.vault = vault
|
||||
self.entries = entries
|
||||
|
||||
@@ -43,8 +45,8 @@ class LockScreenWindow(toga.Window):
|
||||
except Exception as exc: # pragma: no cover - GUI error handling
|
||||
self.message.text = str(exc)
|
||||
return
|
||||
main = MainWindow(self.app, self.vault, self.entries)
|
||||
self.app.main_window = main
|
||||
main = MainWindow(self.controller, self.vault, self.entries)
|
||||
self.controller.main_window = main
|
||||
main.show()
|
||||
self.close()
|
||||
|
||||
@@ -53,10 +55,12 @@ class MainWindow(toga.Window):
|
||||
"""Main application window showing vault entries."""
|
||||
|
||||
def __init__(
|
||||
self, app: SeedPassApp, vault: VaultService, entries: EntryService
|
||||
self, controller: SeedPassApp, vault: VaultService, entries: EntryService
|
||||
) -> None:
|
||||
super().__init__("SeedPass")
|
||||
self.app = app
|
||||
# ``Window.app`` is reserved for the Toga ``App`` instance. Store the
|
||||
# SeedPass application reference separately.
|
||||
self.controller = controller
|
||||
self.vault = vault
|
||||
self.entries = entries
|
||||
|
||||
@@ -115,7 +119,7 @@ class EntryDialog(toga.Window):
|
||||
self.username_input = toga.TextInput(style=Pack(flex=1))
|
||||
self.url_input = toga.TextInput(style=Pack(flex=1))
|
||||
self.length_input = toga.NumberInput(
|
||||
min_value=8, max_value=128, style=Pack(width=80), value=16
|
||||
min=8, max=128, style=Pack(width=80), value=16
|
||||
)
|
||||
|
||||
save_button = toga.Button(
|
||||
@@ -184,7 +188,8 @@ class SearchDialog(toga.Window):
|
||||
|
||||
|
||||
def build() -> SeedPassApp:
|
||||
return SeedPassApp()
|
||||
"""Return a configured :class:`SeedPassApp` instance."""
|
||||
return SeedPassApp(formal_name="SeedPass", app_id="org.seedpass.gui")
|
||||
|
||||
|
||||
class SeedPassApp(toga.App):
|
||||
@@ -201,4 +206,4 @@ class SeedPassApp(toga.App):
|
||||
|
||||
def main() -> None: # pragma: no cover - GUI bootstrap
|
||||
"""Run the BeeWare application."""
|
||||
SeedPassApp().main_loop()
|
||||
build().main_loop()
|
||||
|
69
src/tests/test_gui_headless.py
Normal file
69
src/tests/test_gui_headless.py
Normal file
@@ -0,0 +1,69 @@
|
||||
import os
|
||||
from types import SimpleNamespace
|
||||
|
||||
import toga
|
||||
|
||||
from seedpass_gui.app import LockScreenWindow, MainWindow, EntryDialog
|
||||
|
||||
|
||||
class FakeVault:
|
||||
def __init__(self):
|
||||
self.called = False
|
||||
|
||||
def unlock(self, request):
|
||||
self.called = True
|
||||
|
||||
|
||||
class FakeEntries:
|
||||
def __init__(self):
|
||||
self.added = []
|
||||
self.modified = []
|
||||
|
||||
def list_entries(self):
|
||||
return []
|
||||
|
||||
def search_entries(self, query):
|
||||
return []
|
||||
|
||||
def add_entry(self, label, length, username=None, url=None):
|
||||
self.added.append((label, length, username, url))
|
||||
return 1
|
||||
|
||||
def modify_entry(self, entry_id, username=None, url=None, label=None):
|
||||
self.modified.append((entry_id, username, url, label))
|
||||
|
||||
|
||||
def setup_module(module):
|
||||
os.environ["TOGA_BACKEND"] = "toga_dummy"
|
||||
import asyncio
|
||||
|
||||
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||
|
||||
|
||||
def test_unlock_creates_main_window():
|
||||
app = toga.App("Test", "org.example")
|
||||
controller = SimpleNamespace(main_window=None)
|
||||
vault = FakeVault()
|
||||
entries = FakeEntries()
|
||||
|
||||
win = LockScreenWindow(controller, vault, entries)
|
||||
win.password_input.value = "pw"
|
||||
win.handle_unlock(None)
|
||||
|
||||
assert vault.called
|
||||
assert isinstance(controller.main_window, MainWindow)
|
||||
|
||||
|
||||
def test_entrydialog_add_calls_service():
|
||||
toga.App("Test2", "org.example2")
|
||||
entries = FakeEntries()
|
||||
main = SimpleNamespace(entries=entries, refresh_entries=lambda: None)
|
||||
|
||||
dlg = EntryDialog(main, None)
|
||||
dlg.label_input.value = "L"
|
||||
dlg.username_input.value = "u"
|
||||
dlg.url_input.value = "x"
|
||||
dlg.length_input.value = 12
|
||||
dlg.save(None)
|
||||
|
||||
assert entries.added == [("L", 12, "u", "x")]
|
Reference in New Issue
Block a user