Compare commits
No commits in common. "9ded9b4a10eab5863ad50142fcb7bbd7e524232b" and "f8909d7fcb6a5d61bdd2d818bc3e8fb772ff5ccc" have entirely different histories.
9ded9b4a10
...
f8909d7fcb
10 changed files with 65 additions and 211 deletions
|
|
@ -3,8 +3,6 @@
|
||||||
* Ensure that adding a document whilst on an older date page, uses that date as its upload date
|
* Ensure that adding a document whilst on an older date page, uses that date as its upload date
|
||||||
* Add 'Created at' to time log table.
|
* Add 'Created at' to time log table.
|
||||||
* Show total hours for the day in the time log table (not just in the widget in sidebar)
|
* Show total hours for the day in the time log table (not just in the widget in sidebar)
|
||||||
* Pomodoro timer is now in the sidebar when toggled on, rather than as a separate dialog, so it stays out of the way
|
|
||||||
* Indent tabs by 4 spaces in code block editor dialog
|
|
||||||
|
|
||||||
# 0.6.1
|
# 0.6.1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,21 +40,9 @@ class CodeEditorWithLineNumbers(QPlainTextEdit):
|
||||||
self.cursorPositionChanged.connect(self._line_number_area.update)
|
self.cursorPositionChanged.connect(self._line_number_area.update)
|
||||||
|
|
||||||
self._update_line_number_area_width()
|
self._update_line_number_area_width()
|
||||||
self._update_tab_stop_width()
|
|
||||||
|
|
||||||
# ---- layout / sizing -------------------------------------------------
|
# ---- layout / sizing -------------------------------------------------
|
||||||
|
|
||||||
def setFont(self, font: QFont) -> None: # type: ignore[override]
|
|
||||||
"""Ensure tab width stays at 4 spaces when the font changes."""
|
|
||||||
super().setFont(font)
|
|
||||||
self._update_tab_stop_width()
|
|
||||||
|
|
||||||
def _update_tab_stop_width(self) -> None:
|
|
||||||
"""Set tab width to 4 spaces."""
|
|
||||||
metrics = QFontMetrics(self.font())
|
|
||||||
# Tab width = width of 4 space characters
|
|
||||||
self.setTabStopDistance(metrics.horizontalAdvance(" ") * 4)
|
|
||||||
|
|
||||||
def line_number_area_width(self) -> int:
|
def line_number_area_width(self) -> int:
|
||||||
# Enough digits for large-ish code blocks.
|
# Enough digits for large-ish code blocks.
|
||||||
digits = max(2, len(str(max(1, self.blockCount()))))
|
digits = max(2, len(str(max(1, self.blockCount()))))
|
||||||
|
|
|
||||||
|
|
@ -1194,30 +1194,22 @@ class MainWindow(QMainWindow):
|
||||||
self.upcoming_reminders._add_reminder()
|
self.upcoming_reminders._add_reminder()
|
||||||
|
|
||||||
def _on_timer_requested(self):
|
def _on_timer_requested(self):
|
||||||
"""Toggle the embedded Pomodoro timer for the current line."""
|
"""Start a Pomodoro timer for the current line."""
|
||||||
action = self.toolBar.actTimer
|
editor = getattr(self, "editor", None)
|
||||||
|
if editor is None:
|
||||||
|
return
|
||||||
|
|
||||||
# Turned on -> start a new timer for the current line
|
# Get the current line text
|
||||||
if action.isChecked():
|
line_text = editor.get_current_line_task_text()
|
||||||
editor = getattr(self, "editor", None)
|
|
||||||
if editor is None:
|
|
||||||
# No editor; immediately reset the toggle
|
|
||||||
action.setChecked(False)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get the current line text
|
if not line_text:
|
||||||
line_text = editor.get_current_line_task_text()
|
line_text = strings._("pomodoro_time_log_default_text")
|
||||||
if not line_text:
|
|
||||||
line_text = strings._("pomodoro_time_log_default_text")
|
|
||||||
|
|
||||||
# Get current date
|
# Get current date
|
||||||
date_iso = self.editor.current_date.toString("yyyy-MM-dd")
|
date_iso = self.editor.current_date.toString("yyyy-MM-dd")
|
||||||
|
|
||||||
# Start the timer embedded in the sidebar
|
# Start the timer
|
||||||
self.pomodoro_manager.start_timer_for_line(line_text, date_iso)
|
self.pomodoro_manager.start_timer_for_line(line_text, date_iso)
|
||||||
else:
|
|
||||||
# Turned off -> cancel any running timer and remove the widget
|
|
||||||
self.pomodoro_manager.cancel_timer()
|
|
||||||
|
|
||||||
def _show_flashing_reminder(self, text: str):
|
def _show_flashing_reminder(self, text: str):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -356,6 +356,7 @@ class MarkdownHighlighter(QSyntaxHighlighter):
|
||||||
for m in re.finditer(r"[☐☑]", text):
|
for m in re.finditer(r"[☐☑]", text):
|
||||||
self._overlay_range(m.start(), 1, self.checkbox_format)
|
self._overlay_range(m.start(), 1, self.checkbox_format)
|
||||||
|
|
||||||
|
# (If you add Unicode bullets later…)
|
||||||
for m in re.finditer(r"•", text):
|
for m in re.finditer(r"•", text):
|
||||||
self._overlay_range(m.start(), 1, self.bullet_format)
|
self._overlay_range(m.start(), 1, self.bullet_format)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ from __future__ import annotations
|
||||||
import math
|
import math
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from PySide6.QtCore import Qt, QTimer, Signal, Slot, QSignalBlocker
|
from PySide6.QtCore import Qt, QTimer, Signal, Slot
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QFrame,
|
QDialog,
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
QLabel,
|
QLabel,
|
||||||
|
|
@ -18,13 +18,16 @@ from .db import DBManager
|
||||||
from .time_log import TimeLogDialog
|
from .time_log import TimeLogDialog
|
||||||
|
|
||||||
|
|
||||||
class PomodoroTimer(QFrame):
|
class PomodoroTimer(QDialog):
|
||||||
"""A simple timer for tracking work time on a specific task."""
|
"""A simple timer dialog for tracking work time on a specific task."""
|
||||||
|
|
||||||
timerStopped = Signal(int, str) # Emits (elapsed_seconds, task_text)
|
timerStopped = Signal(int, str) # Emits (elapsed_seconds, task_text)
|
||||||
|
|
||||||
def __init__(self, task_text: str, parent: Optional[QWidget] = None):
|
def __init__(self, task_text: str, parent: Optional[QWidget] = None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
self.setWindowTitle(strings._("toolbar_pomodoro_timer"))
|
||||||
|
self.setModal(False)
|
||||||
|
self.setMinimumWidth(300)
|
||||||
|
|
||||||
self._task_text = task_text
|
self._task_text = task_text
|
||||||
self._elapsed_seconds = 0
|
self._elapsed_seconds = 0
|
||||||
|
|
@ -40,7 +43,7 @@ class PomodoroTimer(QFrame):
|
||||||
# Timer display
|
# Timer display
|
||||||
self.time_label = QLabel("00:00:00")
|
self.time_label = QLabel("00:00:00")
|
||||||
font = self.time_label.font()
|
font = self.time_label.font()
|
||||||
font.setPointSize(20)
|
font.setPointSize(24)
|
||||||
font.setBold(True)
|
font.setBold(True)
|
||||||
self.time_label.setFont(font)
|
self.time_label.setFont(font)
|
||||||
self.time_label.setAlignment(Qt.AlignCenter)
|
self.time_label.setAlignment(Qt.AlignCenter)
|
||||||
|
|
@ -100,7 +103,7 @@ class PomodoroTimer(QFrame):
|
||||||
self._timer.stop()
|
self._timer.stop()
|
||||||
|
|
||||||
self.timerStopped.emit(self._elapsed_seconds, self._task_text)
|
self.timerStopped.emit(self._elapsed_seconds, self._task_text)
|
||||||
self.close()
|
self.accept()
|
||||||
|
|
||||||
|
|
||||||
class PomodoroManager:
|
class PomodoroManager:
|
||||||
|
|
@ -112,47 +115,17 @@ class PomodoroManager:
|
||||||
self._active_timer: Optional[PomodoroTimer] = None
|
self._active_timer: Optional[PomodoroTimer] = None
|
||||||
|
|
||||||
def start_timer_for_line(self, line_text: str, date_iso: str):
|
def start_timer_for_line(self, line_text: str, date_iso: str):
|
||||||
"""
|
"""Start a new timer for the given line of text."""
|
||||||
Start a new timer for the given line of text and embed it into the
|
# Stop any existing timer
|
||||||
TimeLogWidget in the main window sidebar.
|
if self._active_timer and self._active_timer.isVisible():
|
||||||
"""
|
self._active_timer.close()
|
||||||
# Cancel any existing timer first
|
|
||||||
self.cancel_timer()
|
|
||||||
|
|
||||||
# The timer lives inside the TimeLogWidget in the sidebar
|
# Create new timer
|
||||||
time_log_widget = getattr(self._parent, "time_log", None)
|
self._active_timer = PomodoroTimer(line_text, self._parent)
|
||||||
if time_log_widget is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
self._active_timer = PomodoroTimer(line_text, time_log_widget)
|
|
||||||
self._active_timer.timerStopped.connect(
|
self._active_timer.timerStopped.connect(
|
||||||
lambda seconds, text: self._on_timer_stopped(seconds, text, date_iso)
|
lambda seconds, text: self._on_timer_stopped(seconds, text, date_iso)
|
||||||
)
|
)
|
||||||
|
self._active_timer.show()
|
||||||
# Ask the TimeLogWidget to own and display the widget
|
|
||||||
if hasattr(time_log_widget, "show_pomodoro_widget"):
|
|
||||||
time_log_widget.show_pomodoro_widget(self._active_timer)
|
|
||||||
else:
|
|
||||||
# Fallback – just attach it as a child widget
|
|
||||||
self._active_timer.setParent(time_log_widget)
|
|
||||||
self._active_timer.show()
|
|
||||||
|
|
||||||
def cancel_timer(self):
|
|
||||||
"""Cancel any running timer without logging and remove it from the sidebar."""
|
|
||||||
if not self._active_timer:
|
|
||||||
return
|
|
||||||
|
|
||||||
time_log_widget = getattr(self._parent, "time_log", None)
|
|
||||||
if time_log_widget is not None and hasattr(
|
|
||||||
time_log_widget, "clear_pomodoro_widget"
|
|
||||||
):
|
|
||||||
time_log_widget.clear_pomodoro_widget()
|
|
||||||
else:
|
|
||||||
# Fallback if the widget API doesn't exist
|
|
||||||
self._active_timer.setParent(None)
|
|
||||||
|
|
||||||
self._active_timer.deleteLater()
|
|
||||||
self._active_timer = None
|
|
||||||
|
|
||||||
def _on_timer_stopped(self, elapsed_seconds: int, task_text: str, date_iso: str):
|
def _on_timer_stopped(self, elapsed_seconds: int, task_text: str, date_iso: str):
|
||||||
"""Handle timer stop - open time log dialog with pre-filled data."""
|
"""Handle timer stop - open time log dialog with pre-filled data."""
|
||||||
|
|
@ -164,16 +137,6 @@ class PomodoroManager:
|
||||||
if hours < 0.25:
|
if hours < 0.25:
|
||||||
hours = 0.25
|
hours = 0.25
|
||||||
|
|
||||||
# Untoggle the toolbar button without retriggering the slot
|
|
||||||
tool_bar = getattr(self._parent, "toolBar", None)
|
|
||||||
if tool_bar is not None and hasattr(tool_bar, "actTimer"):
|
|
||||||
blocker = QSignalBlocker(tool_bar.actTimer)
|
|
||||||
tool_bar.actTimer.setChecked(False)
|
|
||||||
del blocker
|
|
||||||
|
|
||||||
# Remove the embedded widget
|
|
||||||
self.cancel_timer()
|
|
||||||
|
|
||||||
# Open time log dialog
|
# Open time log dialog
|
||||||
dlg = TimeLogDialog(
|
dlg = TimeLogDialog(
|
||||||
self._db,
|
self._db,
|
||||||
|
|
@ -192,13 +155,3 @@ class PomodoroManager:
|
||||||
|
|
||||||
# Show the dialog
|
# Show the dialog
|
||||||
dlg.exec()
|
dlg.exec()
|
||||||
|
|
||||||
time_log_widget = getattr(self._parent, "time_log", None)
|
|
||||||
if time_log_widget is not None:
|
|
||||||
# Same behaviour as TimeLogWidget._open_dialog/_open_dialog_log_only:
|
|
||||||
# reload the summary so the TimeLogWidget in sidebar updates its totals
|
|
||||||
time_log_widget._reload_summary()
|
|
||||||
if not time_log_widget.toggle_btn.isChecked():
|
|
||||||
time_log_widget.summary_label.setText(
|
|
||||||
strings._("time_log_collapsed_hint")
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@ class DateHeatmap(QWidget):
|
||||||
fm = painter.fontMetrics()
|
fm = painter.fontMetrics()
|
||||||
|
|
||||||
# --- weekday labels on left -------------------------------------
|
# --- weekday labels on left -------------------------------------
|
||||||
# Python's weekday(): Monday=0 ... Sunday=6
|
# Python's weekday(): Monday=0 ... Sunday=6, same as your rows.
|
||||||
weekday_labels = ["M", "T", "W", "T", "F", "S", "S"]
|
weekday_labels = ["M", "T", "W", "T", "F", "S", "S"]
|
||||||
|
|
||||||
for dow in range(7):
|
for dow in range(7):
|
||||||
|
|
|
||||||
|
|
@ -106,8 +106,6 @@ class TimeLogWidget(QFrame):
|
||||||
self.summary_label = QLabel(strings._("time_log_no_entries"))
|
self.summary_label = QLabel(strings._("time_log_no_entries"))
|
||||||
self.summary_label.setWordWrap(True)
|
self.summary_label.setWordWrap(True)
|
||||||
self.body_layout.addWidget(self.summary_label)
|
self.body_layout.addWidget(self.summary_label)
|
||||||
# Optional embedded Pomodoro timer widget lives underneath the summary.
|
|
||||||
self._pomodoro_widget: Optional[QWidget] = None
|
|
||||||
self.body.setVisible(False)
|
self.body.setVisible(False)
|
||||||
|
|
||||||
main = QVBoxLayout(self)
|
main = QVBoxLayout(self)
|
||||||
|
|
@ -123,30 +121,6 @@ class TimeLogWidget(QFrame):
|
||||||
if not self.toggle_btn.isChecked():
|
if not self.toggle_btn.isChecked():
|
||||||
self.summary_label.setText(strings._("time_log_collapsed_hint"))
|
self.summary_label.setText(strings._("time_log_collapsed_hint"))
|
||||||
|
|
||||||
def show_pomodoro_widget(self, widget: QWidget) -> None:
|
|
||||||
"""Embed Pomodoro timer widget in the body area."""
|
|
||||||
if self._pomodoro_widget is not None:
|
|
||||||
self.body_layout.removeWidget(self._pomodoro_widget)
|
|
||||||
self._pomodoro_widget.deleteLater()
|
|
||||||
|
|
||||||
self._pomodoro_widget = widget
|
|
||||||
self.body_layout.addWidget(widget)
|
|
||||||
widget.show()
|
|
||||||
|
|
||||||
# Ensure the body is visible so the timer is obvious
|
|
||||||
self.body.setVisible(True)
|
|
||||||
self.toggle_btn.setChecked(True)
|
|
||||||
self.toggle_btn.setArrowType(Qt.DownArrow)
|
|
||||||
|
|
||||||
def clear_pomodoro_widget(self) -> None:
|
|
||||||
"""Remove any embedded Pomodoro timer widget."""
|
|
||||||
if self._pomodoro_widget is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.body_layout.removeWidget(self._pomodoro_widget)
|
|
||||||
self._pomodoro_widget.deleteLater()
|
|
||||||
self._pomodoro_widget = None
|
|
||||||
|
|
||||||
# ----- internals ---------------------------------------------------
|
# ----- internals ---------------------------------------------------
|
||||||
|
|
||||||
def _on_toggle(self, checked: bool) -> None:
|
def _on_toggle(self, checked: bool) -> None:
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,6 @@ class ToolBar(QToolBar):
|
||||||
# Focus timer
|
# Focus timer
|
||||||
self.actTimer = QAction("⌛", self)
|
self.actTimer = QAction("⌛", self)
|
||||||
self.actTimer.setToolTip(strings._("toolbar_pomodoro_timer"))
|
self.actTimer.setToolTip(strings._("toolbar_pomodoro_timer"))
|
||||||
self.actTimer.setCheckable(True)
|
|
||||||
self.actTimer.triggered.connect(self.timerRequested)
|
self.actTimer.triggered.connect(self.timerRequested)
|
||||||
|
|
||||||
# Documents
|
# Documents
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "bouquin"
|
name = "bouquin"
|
||||||
version = "0.6.2"
|
version = "0.6.1"
|
||||||
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"
|
||||||
|
|
|
||||||
|
|
@ -1,54 +1,6 @@
|
||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock, patch
|
||||||
from bouquin.pomodoro_timer import PomodoroTimer, PomodoroManager
|
from bouquin.pomodoro_timer import PomodoroTimer, PomodoroManager
|
||||||
from bouquin.theme import ThemeManager, ThemeConfig, Theme
|
from bouquin.theme import ThemeManager, ThemeConfig, Theme
|
||||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QToolBar, QLabel
|
|
||||||
from PySide6.QtGui import QAction
|
|
||||||
|
|
||||||
|
|
||||||
class DummyTimeLogWidget(QWidget):
|
|
||||||
"""Minimal stand-in for the real TimeLogWidget used by PomodoroManager."""
|
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
|
||||||
super().__init__(parent)
|
|
||||||
self.layout = QVBoxLayout(self)
|
|
||||||
self.summary_label = QLabel(self)
|
|
||||||
# toggle_btn and _reload_summary are used by PomodoroManager._on_timer_stopped
|
|
||||||
self.toggle_btn = Mock()
|
|
||||||
self.toggle_btn.isChecked.return_value = True
|
|
||||||
|
|
||||||
def show_pomodoro_widget(self, widget):
|
|
||||||
# Manager calls this when embedding the timer
|
|
||||||
if widget is not None:
|
|
||||||
self.layout.addWidget(widget)
|
|
||||||
|
|
||||||
def clear_pomodoro_widget(self):
|
|
||||||
# Manager calls this when removing the embedded timer
|
|
||||||
while self.layout.count():
|
|
||||||
item = self.layout.takeAt(0)
|
|
||||||
w = item.widget()
|
|
||||||
if w is not None:
|
|
||||||
w.setParent(None)
|
|
||||||
|
|
||||||
def _reload_summary(self):
|
|
||||||
# Called after TimeLogDialog closes; no-op is fine for tests
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class DummyMainWindow(QWidget):
|
|
||||||
"""Minimal stand-in for MainWindow that PomodoroManager expects."""
|
|
||||||
|
|
||||||
def __init__(self, app, parent=None):
|
|
||||||
super().__init__(parent)
|
|
||||||
# Sidebar time log widget
|
|
||||||
self.time_log = DummyTimeLogWidget(self)
|
|
||||||
|
|
||||||
# Toolbar with an actTimer QAction so QSignalBlocker works
|
|
||||||
self.toolBar = QToolBar(self)
|
|
||||||
self.toolBar.actTimer = QAction(self)
|
|
||||||
self.toolBar.addAction(self.toolBar.actTimer)
|
|
||||||
|
|
||||||
# Themes attribute used when constructing TimeLogDialog
|
|
||||||
self.themes = ThemeManager(app, ThemeConfig(theme=Theme.LIGHT))
|
|
||||||
|
|
||||||
|
|
||||||
def test_pomodoro_timer_init(qtbot, app, fresh_db):
|
def test_pomodoro_timer_init(qtbot, app, fresh_db):
|
||||||
|
|
@ -196,6 +148,15 @@ def test_pomodoro_timer_modal_state(qtbot, app):
|
||||||
assert timer.isModal() is False
|
assert timer.isModal() is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_pomodoro_timer_window_title(qtbot, app):
|
||||||
|
"""Test timer window title."""
|
||||||
|
timer = PomodoroTimer("Test task")
|
||||||
|
qtbot.addWidget(timer)
|
||||||
|
|
||||||
|
# Window title should contain some reference to timer/pomodoro
|
||||||
|
assert len(timer.windowTitle()) > 0
|
||||||
|
|
||||||
|
|
||||||
def test_pomodoro_manager_init(app, fresh_db):
|
def test_pomodoro_manager_init(app, fresh_db):
|
||||||
"""Test PomodoroManager initialization."""
|
"""Test PomodoroManager initialization."""
|
||||||
parent = Mock()
|
parent = Mock()
|
||||||
|
|
@ -208,10 +169,10 @@ def test_pomodoro_manager_init(app, fresh_db):
|
||||||
|
|
||||||
def test_pomodoro_manager_start_timer(qtbot, app, fresh_db):
|
def test_pomodoro_manager_start_timer(qtbot, app, fresh_db):
|
||||||
"""Test starting a timer through the manager."""
|
"""Test starting a timer through the manager."""
|
||||||
parent = DummyMainWindow(app)
|
from PySide6.QtWidgets import QWidget
|
||||||
qtbot.addWidget(parent)
|
|
||||||
qtbot.addWidget(parent.time_log)
|
|
||||||
|
|
||||||
|
parent = QWidget()
|
||||||
|
qtbot.addWidget(parent)
|
||||||
manager = PomodoroManager(fresh_db, parent)
|
manager = PomodoroManager(fresh_db, parent)
|
||||||
|
|
||||||
line_text = "Important task"
|
line_text = "Important task"
|
||||||
|
|
@ -221,16 +182,15 @@ def test_pomodoro_manager_start_timer(qtbot, app, fresh_db):
|
||||||
|
|
||||||
assert manager._active_timer is not None
|
assert manager._active_timer is not None
|
||||||
assert manager._active_timer._task_text == line_text
|
assert manager._active_timer._task_text == line_text
|
||||||
# Timer should be embedded in the sidebar time log widget
|
qtbot.addWidget(manager._active_timer)
|
||||||
assert manager._active_timer.parent() is parent.time_log
|
|
||||||
|
|
||||||
|
|
||||||
def test_pomodoro_manager_replace_active_timer(qtbot, app, fresh_db):
|
def test_pomodoro_manager_replace_active_timer(qtbot, app, fresh_db):
|
||||||
"""Test that starting a new timer closes/replaces the previous one."""
|
"""Test that starting a new timer closes the previous one."""
|
||||||
parent = DummyMainWindow(app)
|
from PySide6.QtWidgets import QWidget
|
||||||
qtbot.addWidget(parent)
|
|
||||||
qtbot.addWidget(parent.time_log)
|
|
||||||
|
|
||||||
|
parent = QWidget()
|
||||||
|
qtbot.addWidget(parent)
|
||||||
manager = PomodoroManager(fresh_db, parent)
|
manager = PomodoroManager(fresh_db, parent)
|
||||||
|
|
||||||
# Start first timer
|
# Start first timer
|
||||||
|
|
@ -246,20 +206,16 @@ def test_pomodoro_manager_replace_active_timer(qtbot, app, fresh_db):
|
||||||
|
|
||||||
assert first_timer is not second_timer
|
assert first_timer is not second_timer
|
||||||
assert second_timer._task_text == "Task 2"
|
assert second_timer._task_text == "Task 2"
|
||||||
assert second_timer.parent() is parent.time_log
|
|
||||||
|
|
||||||
|
|
||||||
def test_pomodoro_manager_on_timer_stopped_minimum_hours(
|
def test_pomodoro_manager_on_timer_stopped_minimum_hours(
|
||||||
qtbot, app, fresh_db, monkeypatch
|
qtbot, app, fresh_db, monkeypatch
|
||||||
):
|
):
|
||||||
"""Timer stopped with very short time logs should enforce minimum hours."""
|
"""Test that timer stopped with very short time logs minimum hours."""
|
||||||
parent = DummyMainWindow(app)
|
parent = Mock()
|
||||||
qtbot.addWidget(parent)
|
|
||||||
qtbot.addWidget(parent.time_log)
|
|
||||||
|
|
||||||
manager = PomodoroManager(fresh_db, parent)
|
manager = PomodoroManager(fresh_db, parent)
|
||||||
|
|
||||||
# Mock TimeLogDialog to avoid showing it
|
# Mock TimeLogDialog to avoid actually showing it
|
||||||
mock_dialog = Mock()
|
mock_dialog = Mock()
|
||||||
mock_dialog.hours_spin = Mock()
|
mock_dialog.hours_spin = Mock()
|
||||||
mock_dialog.note = Mock()
|
mock_dialog.note = Mock()
|
||||||
|
|
@ -275,11 +231,8 @@ def test_pomodoro_manager_on_timer_stopped_minimum_hours(
|
||||||
|
|
||||||
|
|
||||||
def test_pomodoro_manager_on_timer_stopped_rounding(qtbot, app, fresh_db, monkeypatch):
|
def test_pomodoro_manager_on_timer_stopped_rounding(qtbot, app, fresh_db, monkeypatch):
|
||||||
"""Elapsed time should be rounded up to the nearest 0.25 hours."""
|
"""Test that elapsed time is properly rounded to decimal hours."""
|
||||||
parent = DummyMainWindow(app)
|
parent = Mock()
|
||||||
qtbot.addWidget(parent)
|
|
||||||
qtbot.addWidget(parent.time_log)
|
|
||||||
|
|
||||||
manager = PomodoroManager(fresh_db, parent)
|
manager = PomodoroManager(fresh_db, parent)
|
||||||
|
|
||||||
mock_dialog = Mock()
|
mock_dialog = Mock()
|
||||||
|
|
@ -288,25 +241,21 @@ def test_pomodoro_manager_on_timer_stopped_rounding(qtbot, app, fresh_db, monkey
|
||||||
mock_dialog.exec = Mock()
|
mock_dialog.exec = Mock()
|
||||||
|
|
||||||
with patch("bouquin.pomodoro_timer.TimeLogDialog", return_value=mock_dialog):
|
with patch("bouquin.pomodoro_timer.TimeLogDialog", return_value=mock_dialog):
|
||||||
# 1800 seconds (30 min) should round up to 0.5
|
# Test with 1800 seconds (30 minutes)
|
||||||
manager._on_timer_stopped(1800, "Task", "2024-01-15")
|
manager._on_timer_stopped(1800, "Task", "2024-01-15")
|
||||||
|
|
||||||
mock_dialog.hours_spin.setValue.assert_called_once()
|
mock_dialog.hours_spin.setValue.assert_called_once()
|
||||||
hours_set = mock_dialog.hours_spin.setValue.call_args[0][0]
|
hours_set = mock_dialog.hours_spin.setValue.call_args[0][0]
|
||||||
|
# Should round up and be a multiple of 0.25
|
||||||
assert hours_set > 0
|
assert hours_set > 0
|
||||||
# Should be a multiple of 0.25
|
assert hours_set * 4 == int(hours_set * 4) # Multiple of 0.25
|
||||||
assert hours_set * 4 == int(hours_set * 4)
|
|
||||||
|
|
||||||
|
|
||||||
def test_pomodoro_manager_on_timer_stopped_prefills_note(
|
def test_pomodoro_manager_on_timer_stopped_prefills_note(
|
||||||
qtbot, app, fresh_db, monkeypatch
|
qtbot, app, fresh_db, monkeypatch
|
||||||
):
|
):
|
||||||
"""Timer stopped should pre-fill the note in the time log dialog."""
|
"""Test that timer stopped pre-fills the note in time log dialog."""
|
||||||
parent = DummyMainWindow(app)
|
parent = Mock()
|
||||||
qtbot.addWidget(parent)
|
|
||||||
qtbot.addWidget(parent.time_log)
|
|
||||||
|
|
||||||
manager = PomodoroManager(fresh_db, parent)
|
manager = PomodoroManager(fresh_db, parent)
|
||||||
|
|
||||||
mock_dialog = Mock()
|
mock_dialog = Mock()
|
||||||
|
|
@ -325,11 +274,12 @@ def test_pomodoro_manager_on_timer_stopped_prefills_note(
|
||||||
def test_pomodoro_manager_timer_stopped_signal_connection(
|
def test_pomodoro_manager_timer_stopped_signal_connection(
|
||||||
qtbot, app, fresh_db, monkeypatch
|
qtbot, app, fresh_db, monkeypatch
|
||||||
):
|
):
|
||||||
"""Timer's stop button should result in TimeLogDialog being executed."""
|
"""Test that timer stopped signal is properly connected."""
|
||||||
parent = DummyMainWindow(app)
|
from PySide6.QtWidgets import QWidget
|
||||||
qtbot.addWidget(parent)
|
|
||||||
qtbot.addWidget(parent.time_log)
|
|
||||||
|
|
||||||
|
parent = QWidget()
|
||||||
|
parent.themes = ThemeManager(app, ThemeConfig(theme=Theme.LIGHT))
|
||||||
|
qtbot.addWidget(parent)
|
||||||
manager = PomodoroManager(fresh_db, parent)
|
manager = PomodoroManager(fresh_db, parent)
|
||||||
|
|
||||||
# Mock TimeLogDialog
|
# Mock TimeLogDialog
|
||||||
|
|
@ -343,12 +293,11 @@ def test_pomodoro_manager_timer_stopped_signal_connection(
|
||||||
timer = manager._active_timer
|
timer = manager._active_timer
|
||||||
qtbot.addWidget(timer)
|
qtbot.addWidget(timer)
|
||||||
|
|
||||||
# Simulate timer having run for a bit
|
# Simulate timer stopped
|
||||||
timer._elapsed_seconds = 1000
|
timer._elapsed_seconds = 1000
|
||||||
|
|
||||||
# Clicking "Stop and log" should emit timerStopped and open the dialog
|
|
||||||
timer._stop_and_log()
|
timer._stop_and_log()
|
||||||
|
|
||||||
|
# TimeLogDialog should have been created
|
||||||
assert mock_dialog.exec.called
|
assert mock_dialog.exec.called
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue