Add the ability to choose the database path at startup. Add more tests. Add bandit
This commit is contained in:
parent
8c7226964a
commit
6bc5b66d3f
16 changed files with 297 additions and 97 deletions
|
|
@ -15,7 +15,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
apt-get update
|
apt-get update
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||||
black pyflakes3 vulture
|
black pyflakes3 vulture python3-bandit
|
||||||
|
|
||||||
- name: Run linters
|
- name: Run linters
|
||||||
run: |
|
run: |
|
||||||
|
|
@ -24,3 +24,4 @@ jobs:
|
||||||
pyflakes3 bouquin/*
|
pyflakes3 bouquin/*
|
||||||
pyflakes3 tests/*
|
pyflakes3 tests/*
|
||||||
vulture
|
vulture
|
||||||
|
bandit -s B110 -r bouquin/
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,8 @@
|
||||||
|
# 0.3.2
|
||||||
|
|
||||||
|
* Add weekday letters on left axis of Statistics page
|
||||||
|
* Add the ability to choose the database path at startup
|
||||||
|
|
||||||
# 0.3.1
|
# 0.3.1
|
||||||
|
|
||||||
* Make it possible to add a tag from the Tag Browser
|
* Make it possible to add a tag from the Tag Browser
|
||||||
|
|
|
||||||
|
|
@ -433,7 +433,7 @@ class DBManager:
|
||||||
"""
|
"""
|
||||||
if not name:
|
if not name:
|
||||||
return "#CCCCCC"
|
return "#CCCCCC"
|
||||||
h = int(hashlib.sha1(name.encode("utf-8")).hexdigest()[:8], 16)
|
h = int(hashlib.sha1(name.encode("utf-8")).hexdigest()[:8], 16) # nosec
|
||||||
return _TAG_COLORS[h % len(_TAG_COLORS)]
|
return _TAG_COLORS[h % len(_TAG_COLORS)]
|
||||||
|
|
||||||
# -------- Tags: per-page -------------------------------------------
|
# -------- Tags: per-page -------------------------------------------
|
||||||
|
|
@ -514,7 +514,7 @@ class DBManager:
|
||||||
SELECT id, name
|
SELECT id, name
|
||||||
FROM tags
|
FROM tags
|
||||||
WHERE name IN ({placeholders});
|
WHERE name IN ({placeholders});
|
||||||
""",
|
""", # nosec
|
||||||
tuple(final_tag_names),
|
tuple(final_tag_names),
|
||||||
).fetchall()
|
).fetchall()
|
||||||
ids_by_name = {r["name"]: r["id"] for r in rows}
|
ids_by_name = {r["name"]: r["id"] for r in rows}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QDialog,
|
QDialog,
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
|
QHBoxLayout,
|
||||||
QLabel,
|
QLabel,
|
||||||
QLineEdit,
|
QLineEdit,
|
||||||
QPushButton,
|
QPushButton,
|
||||||
QDialogButtonBox,
|
QDialogButtonBox,
|
||||||
|
QFileDialog,
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import strings
|
from . import strings
|
||||||
|
|
@ -18,32 +22,85 @@ class KeyPrompt(QDialog):
|
||||||
parent=None,
|
parent=None,
|
||||||
title: str = strings._("key_prompt_enter_key"),
|
title: str = strings._("key_prompt_enter_key"),
|
||||||
message: str = strings._("key_prompt_enter_key"),
|
message: str = strings._("key_prompt_enter_key"),
|
||||||
|
initial_db_path: str | Path | None = None,
|
||||||
|
show_db_change: bool = False,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Prompt the user for the key required to decrypt the database.
|
Prompt the user for the key required to decrypt the database.
|
||||||
|
|
||||||
Used when opening the app, unlocking the idle locked screen,
|
Used when opening the app, unlocking the idle locked screen,
|
||||||
or when rekeying.
|
or when rekeying.
|
||||||
|
|
||||||
|
If show_db_change is true, also show a QFileDialog allowing to
|
||||||
|
select a database file, else the default from settings is used.
|
||||||
"""
|
"""
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.setWindowTitle(title)
|
self.setWindowTitle(title)
|
||||||
|
|
||||||
|
self._db_path: Path | None = Path(initial_db_path) if initial_db_path else None
|
||||||
|
|
||||||
v = QVBoxLayout(self)
|
v = QVBoxLayout(self)
|
||||||
|
|
||||||
v.addWidget(QLabel(message))
|
v.addWidget(QLabel(message))
|
||||||
self.edit = QLineEdit()
|
|
||||||
self.edit.setEchoMode(QLineEdit.Password)
|
# DB chooser
|
||||||
v.addWidget(self.edit)
|
self.path_edit: QLineEdit | None = None
|
||||||
|
if show_db_change:
|
||||||
|
path_row = QHBoxLayout()
|
||||||
|
self.path_edit = QLineEdit()
|
||||||
|
if self._db_path is not None:
|
||||||
|
self.path_edit.setText(str(self._db_path))
|
||||||
|
|
||||||
|
browse_btn = QPushButton(strings._("select_notebook"))
|
||||||
|
|
||||||
|
def _browse():
|
||||||
|
start_dir = str(self._db_path or "")
|
||||||
|
fname, _ = QFileDialog.getOpenFileName(
|
||||||
|
self,
|
||||||
|
strings._("select_notebook"),
|
||||||
|
start_dir,
|
||||||
|
"SQLCipher DB (*.db);;All files (*)",
|
||||||
|
)
|
||||||
|
if fname:
|
||||||
|
self._db_path = Path(fname)
|
||||||
|
if self.path_edit is not None:
|
||||||
|
self.path_edit.setText(fname)
|
||||||
|
|
||||||
|
browse_btn.clicked.connect(_browse)
|
||||||
|
|
||||||
|
path_row.addWidget(self.path_edit, 1)
|
||||||
|
path_row.addWidget(browse_btn)
|
||||||
|
v.addLayout(path_row)
|
||||||
|
|
||||||
|
# Key entry
|
||||||
|
self.key_entry = QLineEdit()
|
||||||
|
self.key_entry.setEchoMode(QLineEdit.Password)
|
||||||
|
v.addWidget(self.key_entry)
|
||||||
|
|
||||||
toggle = QPushButton(strings._("show"))
|
toggle = QPushButton(strings._("show"))
|
||||||
toggle.setCheckable(True)
|
toggle.setCheckable(True)
|
||||||
toggle.toggled.connect(
|
toggle.toggled.connect(
|
||||||
lambda c: self.edit.setEchoMode(
|
lambda c: self.key_entry.setEchoMode(
|
||||||
QLineEdit.Normal if c else QLineEdit.Password
|
QLineEdit.Normal if c else QLineEdit.Password
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
v.addWidget(toggle)
|
v.addWidget(toggle)
|
||||||
|
|
||||||
bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||||
bb.accepted.connect(self.accept)
|
bb.accepted.connect(self.accept)
|
||||||
bb.rejected.connect(self.reject)
|
bb.rejected.connect(self.reject)
|
||||||
v.addWidget(bb)
|
v.addWidget(bb)
|
||||||
|
|
||||||
|
self.key_entry.setFocus()
|
||||||
|
self.resize(500, self.sizeHint().height())
|
||||||
|
|
||||||
def key(self) -> str:
|
def key(self) -> str:
|
||||||
return self.edit.text()
|
return self.key_entry.text()
|
||||||
|
|
||||||
|
def db_path(self) -> Path | None:
|
||||||
|
"""Return the chosen DB path (or None if unchanged/not shown)."""
|
||||||
|
if self.path_edit is not None:
|
||||||
|
text = self.path_edit.text().strip()
|
||||||
|
if text:
|
||||||
|
return Path(text)
|
||||||
|
return self._db_path
|
||||||
|
|
|
||||||
|
|
@ -147,5 +147,7 @@
|
||||||
"stats_heatmap_metric": "Colour by",
|
"stats_heatmap_metric": "Colour by",
|
||||||
"stats_metric_words": "Words",
|
"stats_metric_words": "Words",
|
||||||
"stats_metric_revisions": "Revisions",
|
"stats_metric_revisions": "Revisions",
|
||||||
"stats_no_data": "No statistics available yet."
|
"stats_no_data": "No statistics available yet.",
|
||||||
|
"select_notebook": "Select notebook"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -329,14 +329,14 @@ class MainWindow(QMainWindow):
|
||||||
try:
|
try:
|
||||||
self.db = DBManager(self.cfg)
|
self.db = DBManager(self.cfg)
|
||||||
ok = self.db.connect()
|
ok = self.db.connect()
|
||||||
|
return ok
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if str(e) == "file is not a database":
|
if str(e) == "file is not a database":
|
||||||
error = strings._("db_key_incorrect")
|
error = strings._("db_key_incorrect")
|
||||||
else:
|
else:
|
||||||
error = str(e)
|
error = str(e)
|
||||||
QMessageBox.critical(self, strings._("db_database_error"), error)
|
QMessageBox.critical(self, strings._("db_database_error"), error)
|
||||||
return False
|
sys.exit(1)
|
||||||
return ok
|
|
||||||
|
|
||||||
def _prompt_for_key_until_valid(self, first_time: bool) -> bool:
|
def _prompt_for_key_until_valid(self, first_time: bool) -> bool:
|
||||||
"""
|
"""
|
||||||
|
|
@ -349,10 +349,20 @@ class MainWindow(QMainWindow):
|
||||||
title = strings._("unlock_encrypted_notebook")
|
title = strings._("unlock_encrypted_notebook")
|
||||||
message = strings._("unlock_encrypted_notebook_explanation")
|
message = strings._("unlock_encrypted_notebook_explanation")
|
||||||
while True:
|
while True:
|
||||||
dlg = KeyPrompt(self, title, message)
|
dlg = KeyPrompt(
|
||||||
|
self, title, message, initial_db_path=self.cfg.path, show_db_change=True
|
||||||
|
)
|
||||||
if dlg.exec() != QDialog.Accepted:
|
if dlg.exec() != QDialog.Accepted:
|
||||||
return False
|
return False
|
||||||
self.cfg.key = dlg.key()
|
self.cfg.key = dlg.key()
|
||||||
|
|
||||||
|
# Update DB path if the user changed it
|
||||||
|
new_path = dlg.db_path()
|
||||||
|
if new_path is not None and new_path != self.cfg.path:
|
||||||
|
self.cfg.path = new_path
|
||||||
|
# Persist immediately so next run is pre-filled with this file
|
||||||
|
save_db_config(self.cfg)
|
||||||
|
|
||||||
if self._try_connect():
|
if self._try_connect():
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,15 +13,31 @@ def get_settings() -> QSettings:
|
||||||
return QSettings(APP_ORG, APP_NAME)
|
return QSettings(APP_ORG, APP_NAME)
|
||||||
|
|
||||||
|
|
||||||
|
def _default_db_location() -> Path:
|
||||||
|
"""Where we put the notebook if nothing has been configured yet."""
|
||||||
|
base = Path(QStandardPaths.writableLocation(QStandardPaths.AppDataLocation))
|
||||||
|
base.mkdir(parents=True, exist_ok=True)
|
||||||
|
return base / "notebook.db"
|
||||||
|
|
||||||
|
|
||||||
def load_db_config() -> DBConfig:
|
def load_db_config() -> DBConfig:
|
||||||
s = get_settings()
|
s = get_settings()
|
||||||
default_db_path = str(
|
|
||||||
Path(QStandardPaths.writableLocation(QStandardPaths.AppDataLocation))
|
|
||||||
/ "notebook.db"
|
|
||||||
)
|
|
||||||
|
|
||||||
path = Path(s.value("db/path", default_db_path))
|
# --- DB Path -------------------------------------------------------
|
||||||
|
# Prefer the new key; fall back to the legacy one.
|
||||||
|
path_str = s.value("db/default_db", "", type=str)
|
||||||
|
if not path_str:
|
||||||
|
legacy = s.value("db/path", "", type=str)
|
||||||
|
if legacy:
|
||||||
|
path_str = legacy
|
||||||
|
# Optional: migrate and clean up the old key
|
||||||
|
s.setValue("db/default_db", legacy)
|
||||||
|
s.remove("db/path")
|
||||||
|
path = Path(path_str) if path_str else _default_db_location()
|
||||||
|
|
||||||
|
# --- Other settings ------------------------------------------------
|
||||||
key = s.value("db/key", "")
|
key = s.value("db/key", "")
|
||||||
|
|
||||||
idle = s.value("ui/idle_minutes", 15, type=int)
|
idle = s.value("ui/idle_minutes", 15, type=int)
|
||||||
theme = s.value("ui/theme", "system", type=str)
|
theme = s.value("ui/theme", "system", type=str)
|
||||||
move_todos = s.value("ui/move_todos", False, type=bool)
|
move_todos = s.value("ui/move_todos", False, type=bool)
|
||||||
|
|
@ -38,7 +54,7 @@ def load_db_config() -> DBConfig:
|
||||||
|
|
||||||
def save_db_config(cfg: DBConfig) -> None:
|
def save_db_config(cfg: DBConfig) -> None:
|
||||||
s = get_settings()
|
s = get_settings()
|
||||||
s.setValue("db/path", str(cfg.path))
|
s.setValue("db/default_db", str(cfg.path))
|
||||||
s.setValue("db/key", str(cfg.key))
|
s.setValue("db/key", str(cfg.key))
|
||||||
s.setValue("ui/idle_minutes", str(cfg.idle_minutes))
|
s.setValue("ui/idle_minutes", str(cfg.idle_minutes))
|
||||||
s.setValue("ui/theme", str(cfg.theme))
|
s.setValue("ui/theme", str(cfg.theme))
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,7 @@ from PySide6.QtWidgets import (
|
||||||
QLabel,
|
QLabel,
|
||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
QWidget,
|
|
||||||
QLineEdit,
|
|
||||||
QPushButton,
|
QPushButton,
|
||||||
QFileDialog,
|
|
||||||
QDialogButtonBox,
|
QDialogButtonBox,
|
||||||
QRadioButton,
|
QRadioButton,
|
||||||
QSizePolicy,
|
QSizePolicy,
|
||||||
|
|
@ -47,7 +44,7 @@ class SettingsDialog(QDialog):
|
||||||
self.setMinimumWidth(560)
|
self.setMinimumWidth(560)
|
||||||
self.setSizeGripEnabled(True)
|
self.setSizeGripEnabled(True)
|
||||||
|
|
||||||
current_settings = load_db_config()
|
self.current_settings = load_db_config()
|
||||||
|
|
||||||
# Add theme selection
|
# Add theme selection
|
||||||
theme_group = QGroupBox(strings._("theme"))
|
theme_group = QGroupBox(strings._("theme"))
|
||||||
|
|
@ -58,7 +55,7 @@ class SettingsDialog(QDialog):
|
||||||
self.theme_dark = QRadioButton(strings._("dark"))
|
self.theme_dark = QRadioButton(strings._("dark"))
|
||||||
|
|
||||||
# Load current theme from settings
|
# Load current theme from settings
|
||||||
current_theme = current_settings.theme
|
current_theme = self.current_settings.theme
|
||||||
if current_theme == Theme.DARK.value:
|
if current_theme == Theme.DARK.value:
|
||||||
self.theme_dark.setChecked(True)
|
self.theme_dark.setChecked(True)
|
||||||
elif current_theme == Theme.LIGHT.value:
|
elif current_theme == Theme.LIGHT.value:
|
||||||
|
|
@ -80,7 +77,7 @@ class SettingsDialog(QDialog):
|
||||||
|
|
||||||
self.locale_combobox = QComboBox()
|
self.locale_combobox = QComboBox()
|
||||||
self.locale_combobox.addItems(strings._AVAILABLE)
|
self.locale_combobox.addItems(strings._AVAILABLE)
|
||||||
self.locale_combobox.setCurrentText(current_settings.locale)
|
self.locale_combobox.setCurrentText(self.current_settings.locale)
|
||||||
locale_layout.addWidget(self.locale_combobox, 0, Qt.AlignLeft)
|
locale_layout.addWidget(self.locale_combobox, 0, Qt.AlignLeft)
|
||||||
|
|
||||||
# Explanation for locale
|
# Explanation for locale
|
||||||
|
|
@ -104,26 +101,12 @@ class SettingsDialog(QDialog):
|
||||||
self.move_todos = QCheckBox(
|
self.move_todos = QCheckBox(
|
||||||
strings._("move_yesterdays_unchecked_todos_to_today_on_startup")
|
strings._("move_yesterdays_unchecked_todos_to_today_on_startup")
|
||||||
)
|
)
|
||||||
self.move_todos.setChecked(current_settings.move_todos)
|
self.move_todos.setChecked(self.current_settings.move_todos)
|
||||||
self.move_todos.setCursor(Qt.PointingHandCursor)
|
self.move_todos.setCursor(Qt.PointingHandCursor)
|
||||||
|
|
||||||
behaviour_layout.addWidget(self.move_todos)
|
behaviour_layout.addWidget(self.move_todos)
|
||||||
form.addRow(behaviour_group)
|
form.addRow(behaviour_group)
|
||||||
|
|
||||||
self.path_edit = QLineEdit(str(self._cfg.path))
|
|
||||||
self.path_edit.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
|
||||||
browse_btn = QPushButton(strings._("browse"))
|
|
||||||
browse_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
|
||||||
browse_btn.clicked.connect(self._browse)
|
|
||||||
path_row = QWidget()
|
|
||||||
h = QHBoxLayout(path_row)
|
|
||||||
h.setContentsMargins(0, 0, 0, 0)
|
|
||||||
h.addWidget(self.path_edit, 1)
|
|
||||||
h.addWidget(browse_btn, 0)
|
|
||||||
h.setStretch(0, 1)
|
|
||||||
h.setStretch(1, 0)
|
|
||||||
form.addRow(strings._("database_path"), path_row)
|
|
||||||
|
|
||||||
# Encryption settings
|
# Encryption settings
|
||||||
enc_group = QGroupBox(strings._("encryption"))
|
enc_group = QGroupBox(strings._("encryption"))
|
||||||
enc = QVBoxLayout(enc_group)
|
enc = QVBoxLayout(enc_group)
|
||||||
|
|
@ -132,7 +115,7 @@ class SettingsDialog(QDialog):
|
||||||
|
|
||||||
# Checkbox to remember key
|
# Checkbox to remember key
|
||||||
self.save_key_btn = QCheckBox(strings._("remember_key"))
|
self.save_key_btn = QCheckBox(strings._("remember_key"))
|
||||||
self.key = current_settings.key or ""
|
self.key = self.current_settings.key or ""
|
||||||
self.save_key_btn.setChecked(bool(self.key))
|
self.save_key_btn.setChecked(bool(self.key))
|
||||||
self.save_key_btn.setCursor(Qt.PointingHandCursor)
|
self.save_key_btn.setCursor(Qt.PointingHandCursor)
|
||||||
self.save_key_btn.toggled.connect(self._save_key_btn_clicked)
|
self.save_key_btn.toggled.connect(self._save_key_btn_clicked)
|
||||||
|
|
@ -236,16 +219,6 @@ class SettingsDialog(QDialog):
|
||||||
v.addLayout(form)
|
v.addLayout(form)
|
||||||
v.addWidget(bb, 0, Qt.AlignRight)
|
v.addWidget(bb, 0, Qt.AlignRight)
|
||||||
|
|
||||||
def _browse(self):
|
|
||||||
p, _ = QFileDialog.getSaveFileName(
|
|
||||||
self,
|
|
||||||
strings._("database_path"),
|
|
||||||
self.path_edit.text(),
|
|
||||||
"(*.db);;(*)",
|
|
||||||
)
|
|
||||||
if p:
|
|
||||||
self.path_edit.setText(p)
|
|
||||||
|
|
||||||
def _save(self):
|
def _save(self):
|
||||||
# Save the selected theme into QSettings
|
# Save the selected theme into QSettings
|
||||||
if self.theme_dark.isChecked():
|
if self.theme_dark.isChecked():
|
||||||
|
|
@ -258,7 +231,7 @@ class SettingsDialog(QDialog):
|
||||||
key_to_save = self.key if self.save_key_btn.isChecked() else ""
|
key_to_save = self.key if self.save_key_btn.isChecked() else ""
|
||||||
|
|
||||||
self._cfg = DBConfig(
|
self._cfg = DBConfig(
|
||||||
path=Path(self.path_edit.text()),
|
path=Path(self.current_settings.path),
|
||||||
key=key_to_save,
|
key=key_to_save,
|
||||||
idle_minutes=self.idle_spin.value(),
|
idle_minutes=self.idle_spin.value(),
|
||||||
theme=selected_theme.value,
|
theme=selected_theme.value,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "bouquin"
|
name = "bouquin"
|
||||||
version = "0.3.1"
|
version = "0.3.2"
|
||||||
description = "Bouquin is a simple, opinionated notebook application written in Python, PyQt and SQLCipher."
|
description = "Bouquin is a simple, opinionated notebook application written in Python, PyQt and SQLCipher."
|
||||||
authors = ["Miguel Jacq <mig@mig5.net>"]
|
authors = ["Miguel Jacq <mig@mig5.net>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,10 @@ def isolate_qsettings(tmp_path_factory):
|
||||||
def tmp_db_cfg(tmp_path):
|
def tmp_db_cfg(tmp_path):
|
||||||
from bouquin.db import DBConfig
|
from bouquin.db import DBConfig
|
||||||
|
|
||||||
db_path = tmp_path / "notebook.db"
|
default_db = tmp_path / "notebook.db"
|
||||||
key = "test-secret-key"
|
key = "test-secret-key"
|
||||||
return DBConfig(
|
return DBConfig(
|
||||||
path=db_path, key=key, idle_minutes=0, theme="light", move_todos=True
|
path=default_db, key=key, idle_minutes=0, theme="light", move_todos=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,5 @@ def test_key_prompt_roundtrip(qtbot):
|
||||||
kp = KeyPrompt()
|
kp = KeyPrompt()
|
||||||
qtbot.addWidget(kp)
|
qtbot.addWidget(kp)
|
||||||
kp.show()
|
kp.show()
|
||||||
kp.edit.setText("swordfish")
|
kp.key_entry.setText("swordfish")
|
||||||
assert kp.key() == "swordfish"
|
assert kp.key() == "swordfish"
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ from unittest.mock import Mock, patch
|
||||||
|
|
||||||
def test_main_window_loads_and_saves(qtbot, app, tmp_db_cfg, fresh_db):
|
def test_main_window_loads_and_saves(qtbot, app, tmp_db_cfg, fresh_db):
|
||||||
s = get_settings()
|
s = get_settings()
|
||||||
s.setValue("db/path", str(tmp_db_cfg.path))
|
s.setValue("db/default_db", str(tmp_db_cfg.path))
|
||||||
s.setValue("db/key", tmp_db_cfg.key)
|
s.setValue("db/key", tmp_db_cfg.key)
|
||||||
s.setValue("ui/idle_minutes", 0)
|
s.setValue("ui/idle_minutes", 0)
|
||||||
s.setValue("ui/theme", "light")
|
s.setValue("ui/theme", "light")
|
||||||
|
|
@ -48,7 +48,7 @@ def test_main_window_loads_and_saves(qtbot, app, tmp_db_cfg, fresh_db):
|
||||||
def _auto_accept_keyprompt():
|
def _auto_accept_keyprompt():
|
||||||
for wdg in QApplication.topLevelWidgets():
|
for wdg in QApplication.topLevelWidgets():
|
||||||
if isinstance(wdg, KeyPrompt):
|
if isinstance(wdg, KeyPrompt):
|
||||||
wdg.edit.setText(tmp_db_cfg.key)
|
wdg.key_entry.setText(tmp_db_cfg.key)
|
||||||
wdg.accept()
|
wdg.accept()
|
||||||
|
|
||||||
w._enter_lock()
|
w._enter_lock()
|
||||||
|
|
@ -59,7 +59,7 @@ def test_main_window_loads_and_saves(qtbot, app, tmp_db_cfg, fresh_db):
|
||||||
|
|
||||||
def test_load_yesterday_todos_moves_items(qtbot, app, tmp_db_cfg, fresh_db):
|
def test_load_yesterday_todos_moves_items(qtbot, app, tmp_db_cfg, fresh_db):
|
||||||
s = get_settings()
|
s = get_settings()
|
||||||
s.setValue("db/path", str(tmp_db_cfg.path))
|
s.setValue("db/default_db", str(tmp_db_cfg.path))
|
||||||
s.setValue("db/key", tmp_db_cfg.key)
|
s.setValue("db/key", tmp_db_cfg.key)
|
||||||
s.setValue("ui/move_todos", True)
|
s.setValue("ui/move_todos", True)
|
||||||
s.setValue("ui/theme", "light")
|
s.setValue("ui/theme", "light")
|
||||||
|
|
@ -122,7 +122,7 @@ def test_export_success_and_error(qtbot, app, fresh_db, tmp_path, monkeypatch):
|
||||||
from bouquin.settings import get_settings
|
from bouquin.settings import get_settings
|
||||||
|
|
||||||
s = get_settings()
|
s = get_settings()
|
||||||
s.setValue("db/path", str(fresh_db.cfg.path))
|
s.setValue("db/default_db", str(fresh_db.cfg.path))
|
||||||
s.setValue("db/key", fresh_db.cfg.key)
|
s.setValue("db/key", fresh_db.cfg.key)
|
||||||
s.setValue("ui/idle_minutes", 0)
|
s.setValue("ui/idle_minutes", 0)
|
||||||
s.setValue("ui/theme", "light")
|
s.setValue("ui/theme", "light")
|
||||||
|
|
@ -196,7 +196,7 @@ def test_backup_path(qtbot, app, fresh_db, tmp_path, monkeypatch):
|
||||||
from bouquin.settings import get_settings
|
from bouquin.settings import get_settings
|
||||||
|
|
||||||
s = get_settings()
|
s = get_settings()
|
||||||
s.setValue("db/path", str(fresh_db.cfg.path))
|
s.setValue("db/default_db", str(fresh_db.cfg.path))
|
||||||
s.setValue("db/key", fresh_db.cfg.key)
|
s.setValue("db/key", fresh_db.cfg.key)
|
||||||
s.setValue("ui/idle_minutes", 0)
|
s.setValue("ui/idle_minutes", 0)
|
||||||
s.setValue("ui/theme", "light")
|
s.setValue("ui/theme", "light")
|
||||||
|
|
@ -251,7 +251,7 @@ def test_close_tab_edges_and_autosave(qtbot, app, fresh_db, monkeypatch):
|
||||||
from bouquin.settings import get_settings
|
from bouquin.settings import get_settings
|
||||||
|
|
||||||
s = get_settings()
|
s = get_settings()
|
||||||
s.setValue("db/path", str(fresh_db.cfg.path))
|
s.setValue("db/default_db", str(fresh_db.cfg.path))
|
||||||
s.setValue("db/key", fresh_db.cfg.key)
|
s.setValue("db/key", fresh_db.cfg.key)
|
||||||
s.setValue("ui/idle_minutes", 0)
|
s.setValue("ui/idle_minutes", 0)
|
||||||
s.setValue("ui/theme", "light")
|
s.setValue("ui/theme", "light")
|
||||||
|
|
@ -472,8 +472,24 @@ def test_try_connect_maps_errors(
|
||||||
mwmod.QMessageBox, "critical", staticmethod(fake_critical), raising=True
|
mwmod.QMessageBox, "critical", staticmethod(fake_critical), raising=True
|
||||||
)
|
)
|
||||||
|
|
||||||
ok = w._try_connect()
|
# Intercept sys.exit so the test process doesn't actually die
|
||||||
assert ok is False
|
exited = {}
|
||||||
|
|
||||||
|
def fake_exit(code=0):
|
||||||
|
exited["code"] = code
|
||||||
|
# mimic real behaviour: raise SystemExit so callers see a fatal exit
|
||||||
|
raise SystemExit(code)
|
||||||
|
|
||||||
|
monkeypatch.setattr(mwmod.sys, "exit", fake_exit, raising=True)
|
||||||
|
|
||||||
|
# _try_connect should now raise SystemExit instead of returning
|
||||||
|
with pytest.raises(SystemExit):
|
||||||
|
w._try_connect()
|
||||||
|
|
||||||
|
# We attempted to exit with code 1
|
||||||
|
assert exited["code"] == 1
|
||||||
|
|
||||||
|
# And we still showed the right error message
|
||||||
assert "database" in shown["title"].lower()
|
assert "database" in shown["title"].lower()
|
||||||
if expect_key_msg:
|
if expect_key_msg:
|
||||||
assert "key" in shown["text"].lower()
|
assert "key" in shown["text"].lower()
|
||||||
|
|
@ -499,6 +515,9 @@ def test_prompt_for_key_cancel_returns_false(qtbot, tmp_db_cfg, app, monkeypatch
|
||||||
def key(self):
|
def key(self):
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
def db_path(self) -> Path | None:
|
||||||
|
return "foo.db"
|
||||||
|
|
||||||
monkeypatch.setattr(mwmod, "KeyPrompt", CancelPrompt, raising=True)
|
monkeypatch.setattr(mwmod, "KeyPrompt", CancelPrompt, raising=True)
|
||||||
assert w._prompt_for_key_until_valid(first_time=False) is False
|
assert w._prompt_for_key_until_valid(first_time=False) is False
|
||||||
|
|
||||||
|
|
@ -517,6 +536,9 @@ def test_prompt_for_key_accept_then_connects(qtbot, tmp_db_cfg, app, monkeypatch
|
||||||
def key(self):
|
def key(self):
|
||||||
return "abc"
|
return "abc"
|
||||||
|
|
||||||
|
def db_path(self) -> Path | None:
|
||||||
|
return "foo.db"
|
||||||
|
|
||||||
monkeypatch.setattr(mwmod, "KeyPrompt", OKPrompt, raising=True)
|
monkeypatch.setattr(mwmod, "KeyPrompt", OKPrompt, raising=True)
|
||||||
monkeypatch.setattr(w, "_try_connect", lambda: True, raising=True)
|
monkeypatch.setattr(w, "_try_connect", lambda: True, raising=True)
|
||||||
assert w._prompt_for_key_until_valid(first_time=True) is True
|
assert w._prompt_for_key_until_valid(first_time=True) is True
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,30 @@ from bouquin.settings import (
|
||||||
from bouquin.db import DBConfig
|
from bouquin.db import DBConfig
|
||||||
|
|
||||||
|
|
||||||
def test_load_and_save_db_config_roundtrip(app, tmp_path):
|
def _clear_db_settings():
|
||||||
s = get_settings()
|
s = get_settings()
|
||||||
for k in ["db/path", "db/key", "ui/idle_minutes", "ui/theme", "ui/move_todos"]:
|
for k in [
|
||||||
|
"db/default_db",
|
||||||
|
"db/path", # legacy key
|
||||||
|
"db/key",
|
||||||
|
"ui/idle_minutes",
|
||||||
|
"ui/theme",
|
||||||
|
"ui/move_todos",
|
||||||
|
"ui/locale",
|
||||||
|
]:
|
||||||
s.remove(k)
|
s.remove(k)
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_and_save_db_config_roundtrip(app, tmp_path):
|
||||||
|
_clear_db_settings()
|
||||||
|
|
||||||
cfg = DBConfig(
|
cfg = DBConfig(
|
||||||
path=tmp_path / "notes.db",
|
path=tmp_path / "notes.db",
|
||||||
key="abc123",
|
key="abc123",
|
||||||
idle_minutes=7,
|
idle_minutes=7,
|
||||||
theme="dark",
|
theme="dark",
|
||||||
move_todos=True,
|
move_todos=True,
|
||||||
|
locale="en",
|
||||||
)
|
)
|
||||||
save_db_config(cfg)
|
save_db_config(cfg)
|
||||||
|
|
||||||
|
|
@ -26,3 +39,21 @@ def test_load_and_save_db_config_roundtrip(app, tmp_path):
|
||||||
assert loaded.idle_minutes == cfg.idle_minutes
|
assert loaded.idle_minutes == cfg.idle_minutes
|
||||||
assert loaded.theme == cfg.theme
|
assert loaded.theme == cfg.theme
|
||||||
assert loaded.move_todos == cfg.move_todos
|
assert loaded.move_todos == cfg.move_todos
|
||||||
|
assert loaded.locale == cfg.locale
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_db_config_migrates_legacy_db_path(app, tmp_path):
|
||||||
|
_clear_db_settings()
|
||||||
|
s = get_settings()
|
||||||
|
|
||||||
|
legacy_path = tmp_path / "legacy.db"
|
||||||
|
s.setValue("db/path", str(legacy_path))
|
||||||
|
|
||||||
|
cfg = load_db_config()
|
||||||
|
|
||||||
|
# Uses the legacy value…
|
||||||
|
assert cfg.path == legacy_path
|
||||||
|
|
||||||
|
# …but also migrates to the new key and clears the old one.
|
||||||
|
assert s.value("db/default_db", "", type=str) == str(legacy_path)
|
||||||
|
assert s.value("db/path", "", type=str) == ""
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ from PySide6.QtCore import QTimer
|
||||||
from PySide6.QtWidgets import QApplication, QMessageBox, QWidget, QDialog
|
from PySide6.QtWidgets import QApplication, QMessageBox, QWidget, QDialog
|
||||||
|
|
||||||
|
|
||||||
def test_settings_dialog_config_roundtrip(qtbot, tmp_db_cfg, fresh_db, tmp_path):
|
def test_settings_dialog_config_roundtrip(qtbot, tmp_db_cfg, fresh_db):
|
||||||
# Provide a parent that exposes a real ThemeManager (dialog calls parent().themes.set(...))
|
# Provide a parent that exposes a real ThemeManager (dialog calls parent().themes.set(...))
|
||||||
app = QApplication.instance()
|
app = QApplication.instance()
|
||||||
parent = QWidget()
|
parent = QWidget()
|
||||||
|
|
@ -17,7 +17,6 @@ def test_settings_dialog_config_roundtrip(qtbot, tmp_db_cfg, fresh_db, tmp_path)
|
||||||
qtbot.addWidget(dlg)
|
qtbot.addWidget(dlg)
|
||||||
dlg.show()
|
dlg.show()
|
||||||
|
|
||||||
dlg.path_edit.setText(str(tmp_path / "alt.db"))
|
|
||||||
dlg.idle_spin.setValue(3)
|
dlg.idle_spin.setValue(3)
|
||||||
dlg.theme_light.setChecked(True)
|
dlg.theme_light.setChecked(True)
|
||||||
dlg.move_todos.setChecked(True)
|
dlg.move_todos.setChecked(True)
|
||||||
|
|
@ -34,7 +33,6 @@ def test_settings_dialog_config_roundtrip(qtbot, tmp_db_cfg, fresh_db, tmp_path)
|
||||||
|
|
||||||
dlg._save()
|
dlg._save()
|
||||||
cfg = dlg.config
|
cfg = dlg.config
|
||||||
assert cfg.path.name == "alt.db"
|
|
||||||
assert cfg.idle_minutes == 3
|
assert cfg.idle_minutes == 3
|
||||||
assert cfg.theme in ("light", "dark", "system")
|
assert cfg.theme in ("light", "dark", "system")
|
||||||
|
|
||||||
|
|
@ -55,7 +53,7 @@ def test_save_key_toggle_roundtrip(qtbot, tmp_db_cfg, fresh_db, app):
|
||||||
def _pump():
|
def _pump():
|
||||||
for w in QApplication.topLevelWidgets():
|
for w in QApplication.topLevelWidgets():
|
||||||
if isinstance(w, KeyPrompt):
|
if isinstance(w, KeyPrompt):
|
||||||
w.edit.setText("supersecret")
|
w.key_entry.setText("supersecret")
|
||||||
w.accept()
|
w.accept()
|
||||||
elif isinstance(w, QMessageBox):
|
elif isinstance(w, QMessageBox):
|
||||||
w.accept()
|
w.accept()
|
||||||
|
|
@ -99,7 +97,7 @@ def test_change_key_mismatch_shows_error(qtbot, tmp_db_cfg, tmp_path, app):
|
||||||
def _pump_popups():
|
def _pump_popups():
|
||||||
for w in QApplication.topLevelWidgets():
|
for w in QApplication.topLevelWidgets():
|
||||||
if isinstance(w, KeyPrompt):
|
if isinstance(w, KeyPrompt):
|
||||||
w.edit.setText(keys.pop(0) if keys else "zzz")
|
w.key_entry.setText(keys.pop(0) if keys else "zzz")
|
||||||
w.accept()
|
w.accept()
|
||||||
elif isinstance(w, QMessageBox):
|
elif isinstance(w, QMessageBox):
|
||||||
w.accept()
|
w.accept()
|
||||||
|
|
@ -141,7 +139,7 @@ def test_change_key_success(qtbot, tmp_path, app):
|
||||||
def _pump():
|
def _pump():
|
||||||
for w in QApplication.topLevelWidgets():
|
for w in QApplication.topLevelWidgets():
|
||||||
if isinstance(w, KeyPrompt):
|
if isinstance(w, KeyPrompt):
|
||||||
w.edit.setText(keys.pop(0) if keys else "newkey")
|
w.key_entry.setText(keys.pop(0) if keys else "newkey")
|
||||||
w.accept()
|
w.accept()
|
||||||
elif isinstance(w, QMessageBox):
|
elif isinstance(w, QMessageBox):
|
||||||
w.accept()
|
w.accept()
|
||||||
|
|
@ -203,27 +201,6 @@ def test_settings_compact_error(qtbot, app, monkeypatch, tmp_db_cfg, fresh_db):
|
||||||
assert called["text"]
|
assert called["text"]
|
||||||
|
|
||||||
|
|
||||||
def test_settings_browse_sets_path(qtbot, app, tmp_path, fresh_db, monkeypatch):
|
|
||||||
parent = QWidget()
|
|
||||||
parent.themes = ThemeManager(app, ThemeConfig(theme=Theme.LIGHT))
|
|
||||||
cfg = DBConfig(
|
|
||||||
path=tmp_path / "x.db", key="k", idle_minutes=0, theme="light", move_todos=True
|
|
||||||
)
|
|
||||||
dlg = SettingsDialog(cfg, fresh_db, parent=parent)
|
|
||||||
qtbot.addWidget(dlg)
|
|
||||||
dlg.show()
|
|
||||||
|
|
||||||
p = tmp_path / "new_file.db"
|
|
||||||
monkeypatch.setattr(
|
|
||||||
sd.QFileDialog,
|
|
||||||
"getSaveFileName",
|
|
||||||
staticmethod(lambda *a, **k: (str(p), "DB Files (*.db)")),
|
|
||||||
raising=False,
|
|
||||||
)
|
|
||||||
dlg._browse()
|
|
||||||
assert dlg.path_edit.text().endswith("new_file.db")
|
|
||||||
|
|
||||||
|
|
||||||
class _Host(QWidget):
|
class _Host(QWidget):
|
||||||
def __init__(self, themes):
|
def __init__(self, themes):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
|
||||||
106
tests/test_statistics_dialog.py
Normal file
106
tests/test_statistics_dialog.py
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
import datetime as _dt
|
||||||
|
|
||||||
|
from PySide6.QtWidgets import QLabel
|
||||||
|
|
||||||
|
from bouquin.statistics_dialog import StatisticsDialog
|
||||||
|
from bouquin import strings
|
||||||
|
|
||||||
|
|
||||||
|
class FakeStatsDB:
|
||||||
|
"""Minimal stub that returns a fixed stats payload."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
d1 = _dt.date(2024, 1, 1)
|
||||||
|
d2 = _dt.date(2024, 1, 2)
|
||||||
|
self.stats = (
|
||||||
|
2, # pages_with_content
|
||||||
|
5, # total_revisions
|
||||||
|
"2024-01-02", # page_most_revisions
|
||||||
|
3, # page_most_revisions_count
|
||||||
|
{d1: 10, d2: 20}, # words_by_date
|
||||||
|
30, # total_words
|
||||||
|
4, # unique_tags
|
||||||
|
"2024-01-02", # page_most_tags
|
||||||
|
2, # page_most_tags_count
|
||||||
|
{d1: 1, d2: 2}, # revisions_by_date
|
||||||
|
)
|
||||||
|
self.called = False
|
||||||
|
|
||||||
|
def gather_stats(self):
|
||||||
|
self.called = True
|
||||||
|
return self.stats
|
||||||
|
|
||||||
|
|
||||||
|
def test_statistics_dialog_populates_fields_and_heatmap(qtbot):
|
||||||
|
# Make sure we have a known language for label texts
|
||||||
|
strings.load_strings("en")
|
||||||
|
|
||||||
|
db = FakeStatsDB()
|
||||||
|
dlg = StatisticsDialog(db)
|
||||||
|
qtbot.addWidget(dlg)
|
||||||
|
dlg.show()
|
||||||
|
|
||||||
|
# Stats were actually requested from the DB
|
||||||
|
assert db.called
|
||||||
|
|
||||||
|
# Window title comes from translations
|
||||||
|
assert dlg.windowTitle() == strings._("statistics")
|
||||||
|
|
||||||
|
# Grab all label texts for simple content checks
|
||||||
|
label_texts = {lbl.text() for lbl in dlg.findChildren(QLabel)}
|
||||||
|
|
||||||
|
# Page with most revisions / tags are rendered as "DATE (COUNT)"
|
||||||
|
assert "2024-01-02 (3)" in label_texts
|
||||||
|
assert "2024-01-02 (2)" in label_texts
|
||||||
|
|
||||||
|
# Heatmap is created and uses "words" by default
|
||||||
|
words_by_date = db.stats[4]
|
||||||
|
revisions_by_date = db.stats[-1]
|
||||||
|
|
||||||
|
assert hasattr(dlg, "_heatmap")
|
||||||
|
assert dlg._heatmap._data == words_by_date
|
||||||
|
|
||||||
|
# Switching the metric to "revisions" should swap the dataset
|
||||||
|
dlg.metric_combo.setCurrentIndex(1) # 0 = words, 1 = revisions
|
||||||
|
qtbot.wait(10)
|
||||||
|
assert dlg._heatmap._data == revisions_by_date
|
||||||
|
|
||||||
|
|
||||||
|
class EmptyStatsDB:
|
||||||
|
"""Stub that returns a 'no data yet' stats payload."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.called = False
|
||||||
|
|
||||||
|
def gather_stats(self):
|
||||||
|
self.called = True
|
||||||
|
return (
|
||||||
|
0, # pages_with_content
|
||||||
|
0, # total_revisions
|
||||||
|
None, # page_most_revisions
|
||||||
|
0,
|
||||||
|
{}, # words_by_date
|
||||||
|
0, # total_words
|
||||||
|
0, # unique_tags
|
||||||
|
None, # page_most_tags
|
||||||
|
0,
|
||||||
|
{}, # revisions_by_date
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_statistics_dialog_no_data_shows_placeholder(qtbot):
|
||||||
|
strings.load_strings("en")
|
||||||
|
|
||||||
|
db = EmptyStatsDB()
|
||||||
|
dlg = StatisticsDialog(db)
|
||||||
|
qtbot.addWidget(dlg)
|
||||||
|
dlg.show()
|
||||||
|
|
||||||
|
assert db.called
|
||||||
|
|
||||||
|
label_texts = [lbl.text() for lbl in dlg.findChildren(QLabel)]
|
||||||
|
assert strings._("stats_no_data") in label_texts
|
||||||
|
|
||||||
|
# When there's no data, the heatmap and metric combo shouldn't exist
|
||||||
|
assert not hasattr(dlg, "metric_combo")
|
||||||
|
assert not hasattr(dlg, "_heatmap")
|
||||||
|
|
@ -12,7 +12,7 @@ from bouquin.history_dialog import HistoryDialog
|
||||||
def test_tabs_open_and_deduplicate(qtbot, app, tmp_db_cfg, fresh_db):
|
def test_tabs_open_and_deduplicate(qtbot, app, tmp_db_cfg, fresh_db):
|
||||||
# point to the temp encrypted DB
|
# point to the temp encrypted DB
|
||||||
s = get_settings()
|
s = get_settings()
|
||||||
s.setValue("db/path", str(tmp_db_cfg.path))
|
s.setValue("db/default_db", str(tmp_db_cfg.path))
|
||||||
s.setValue("db/key", tmp_db_cfg.key)
|
s.setValue("db/key", tmp_db_cfg.key)
|
||||||
|
|
||||||
themes = ThemeManager(app, ThemeConfig(theme=Theme.LIGHT))
|
themes = ThemeManager(app, ThemeConfig(theme=Theme.LIGHT))
|
||||||
|
|
@ -45,7 +45,7 @@ def test_toolbar_signals_dispatch_once_per_click(
|
||||||
qtbot, app, tmp_db_cfg, fresh_db, monkeypatch
|
qtbot, app, tmp_db_cfg, fresh_db, monkeypatch
|
||||||
):
|
):
|
||||||
s = get_settings()
|
s = get_settings()
|
||||||
s.setValue("db/path", str(tmp_db_cfg.path))
|
s.setValue("db/default_db", str(tmp_db_cfg.path))
|
||||||
s.setValue("db/key", tmp_db_cfg.key)
|
s.setValue("db/key", tmp_db_cfg.key)
|
||||||
|
|
||||||
themes = ThemeManager(app, ThemeConfig(theme=Theme.LIGHT))
|
themes = ThemeManager(app, ThemeConfig(theme=Theme.LIGHT))
|
||||||
|
|
@ -116,7 +116,7 @@ def test_history_and_insert_image_not_duplicated(
|
||||||
qtbot, app, tmp_db_cfg, fresh_db, monkeypatch, tmp_path
|
qtbot, app, tmp_db_cfg, fresh_db, monkeypatch, tmp_path
|
||||||
):
|
):
|
||||||
s = get_settings()
|
s = get_settings()
|
||||||
s.setValue("db/path", str(tmp_db_cfg.path))
|
s.setValue("db/default_db", str(tmp_db_cfg.path))
|
||||||
s.setValue("db/key", tmp_db_cfg.key)
|
s.setValue("db/key", tmp_db_cfg.key)
|
||||||
|
|
||||||
themes = ThemeManager(app, ThemeConfig(theme=Theme.LIGHT))
|
themes = ThemeManager(app, ThemeConfig(theme=Theme.LIGHT))
|
||||||
|
|
@ -156,7 +156,7 @@ def test_history_and_insert_image_not_duplicated(
|
||||||
|
|
||||||
def test_highlighter_attached_after_text_load(qtbot, app, tmp_db_cfg, fresh_db):
|
def test_highlighter_attached_after_text_load(qtbot, app, tmp_db_cfg, fresh_db):
|
||||||
s = get_settings()
|
s = get_settings()
|
||||||
s.setValue("db/path", str(tmp_db_cfg.path))
|
s.setValue("db/default_db", str(tmp_db_cfg.path))
|
||||||
s.setValue("db/key", tmp_db_cfg.key)
|
s.setValue("db/key", tmp_db_cfg.key)
|
||||||
|
|
||||||
themes = ThemeManager(app, ThemeConfig(theme=Theme.LIGHT))
|
themes = ThemeManager(app, ThemeConfig(theme=Theme.LIGHT))
|
||||||
|
|
@ -171,7 +171,7 @@ def test_highlighter_attached_after_text_load(qtbot, app, tmp_db_cfg, fresh_db):
|
||||||
|
|
||||||
def test_findbar_works_for_current_tab(qtbot, app, tmp_db_cfg, fresh_db):
|
def test_findbar_works_for_current_tab(qtbot, app, tmp_db_cfg, fresh_db):
|
||||||
s = get_settings()
|
s = get_settings()
|
||||||
s.setValue("db/path", str(tmp_db_cfg.path))
|
s.setValue("db/default_db", str(tmp_db_cfg.path))
|
||||||
s.setValue("db/key", tmp_db_cfg.key)
|
s.setValue("db/key", tmp_db_cfg.key)
|
||||||
|
|
||||||
themes = ThemeManager(app, ThemeConfig(theme=Theme.LIGHT))
|
themes = ThemeManager(app, ThemeConfig(theme=Theme.LIGHT))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue