diff --git a/CHANGELOG.md b/CHANGELOG.md
index 51a3738..15affd7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog
+## 0.1.16
+
+- Updated bar visuals for non-rounded variation
+- WIP Budgets
+- WIP Tests
+
## 0.1.15
- Fix: Deleted categories visible
diff --git a/pyproject.toml b/pyproject.toml
index 704edb3..d48f5d7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "Bagels"
-version = "0.1.15"
+version = "0.1.16"
authors = [
{ name = "Jax", email = "enhancedjax@gmail.com" }
]
@@ -19,7 +19,6 @@ dependencies = [
"blinker==1.8.2",
"click-default-group>=1.2.4",
"click==8.1.7",
- "freezegun>=1.5.1",
"frozenlist==1.5.0",
"idna==3.10",
"itsdangerous==2.2.0",
@@ -73,6 +72,7 @@ dev-dependencies = [
"pytest-xdist>=3.6.1",
"pytest-cov>=5.0.0",
"pytest-textual-snapshot==1.0.0",
+ "time-machine==2.16.0"
]
[tool.hatch.metadata]
diff --git a/src/bagels/app.py b/src/bagels/app.py
index d9ee563..2d2dfde 100644
--- a/src/bagels/app.py
+++ b/src/bagels/app.py
@@ -31,6 +31,7 @@ class App(TextualApp):
BINDINGS = [
(CONFIG.hotkeys.toggle_jump_mode, "toggle_jump_mode", "Jump Mode"),
(CONFIG.hotkeys.home.categories, "go_to_categories", "Categories"),
+ (CONFIG.hotkeys.home.budgets, "go_to_budgets", "Budgets"),
("ctrl+q", "quit", "Quit"),
]
COMMANDS = {AppProvider}
@@ -44,11 +45,12 @@ class App(TextualApp):
"""True if 'jump mode' is currently active, otherwise False."""
# region init
- def __init__(self):
+ def __init__(self, is_testing=False) -> None:
# Initialize available themes with a default
available_themes: dict[str, Theme] = {"galaxy": BUILTIN_THEMES["galaxy"]}
available_themes |= BUILTIN_THEMES
self.themes = available_themes
+ self.is_testing = is_testing
super().__init__()
# Get package metadata directly
@@ -189,12 +191,16 @@ def on_tabs_tab_activated(self, event: Tabs.TabActivated) -> None:
self.mount(page_instance)
def on_resize(self, event: events.Resize) -> None:
- console_size: Size = self.console.size
+ console_size: Size = event.size
aspect_ratio = (console_size.width / 2) / console_size.height
if aspect_ratio < 1:
self.layout = "v"
else:
self.layout = "h"
+ if self.is_testing:
+ self.query_one(".version").update(
+ "Layout: " + self.layout + " " + str(aspect_ratio)
+ )
# region callbacks
# --------------- Callbacks ------------ #
@@ -210,15 +216,20 @@ def action_quit(self) -> None:
def action_go_to_categories(self) -> None:
self.push_screen(CategoriesModal(), callback=self.on_categories_dismissed)
+ def action_go_to_budgets(self) -> None:
+ self.notify("Work in progress!", title="Budgets")
+
def on_categories_dismissed(self, _) -> None:
self.app.refresh(recompose=True)
# region view
# --------------- View --------------- #
def compose(self) -> ComposeResult:
+ version = self.project_info["version"] if not self.is_testing else "vt"
+ user_host = get_user_host_string() if not self.is_testing else "test"
with Container(classes="header"):
- yield Label(f"↪ {self.project_info['name']}", classes="title")
- yield Label(self.project_info["version"], classes="version")
- yield Label(get_user_host_string(), classes="user")
+ yield Label(f"↪ {self.project_info["name"]}", classes="title")
+ yield Label(version, classes="version")
+ yield Label(user_host, classes="user")
yield Home(classes="content")
yield Footer()
diff --git a/src/bagels/components/jump_overlay.py b/src/bagels/components/jump_overlay.py
index 5cdff90..edcff25 100644
--- a/src/bagels/components/jump_overlay.py
+++ b/src/bagels/components/jump_overlay.py
@@ -55,6 +55,7 @@ def on_key(self, key_event: events.Key) -> None:
CONFIG.hotkeys.new,
CONFIG.hotkeys.edit,
CONFIG.hotkeys.delete,
+ CONFIG.hotkeys.home.new_transfer,
]:
key_event.stop()
key_event.prevent_default()
diff --git a/src/bagels/components/modules/records.py b/src/bagels/components/modules/records.py
index f4a2565..243285f 100644
--- a/src/bagels/components/modules/records.py
+++ b/src/bagels/components/modules/records.py
@@ -217,7 +217,7 @@ def _format_record_fields(self, record, flow_icon: str) -> tuple[str, str]:
if record.splits and not self.show_splits:
amount_self = round(
- record.amount - get_record_total_split_amount(record.id)
+ record.amount - get_record_total_split_amount(record.id), 2
)
amount_string = f"{flow_icon} {amount_self}"
else:
@@ -234,7 +234,10 @@ def _add_group_header_row(
def _add_split_rows(self, table: DataTable, record, flow_icon: str) -> None:
color = record.category.color.lower()
- amount_self = round(record.amount - get_record_total_split_amount(record.id), 2)
+ amount_self = round(
+ record.amount - get_record_total_split_amount(record.id),
+ CONFIG.defaults.round_decimals,
+ )
split_flow_icon = (
f"[red]{CONFIG.symbols.amount_negative}[/red]"
if record.isIncome
diff --git a/src/bagels/components/modules/templates.py b/src/bagels/components/modules/templates.py
index e91aecb..a88894c 100644
--- a/src/bagels/components/modules/templates.py
+++ b/src/bagels/components/modules/templates.py
@@ -191,7 +191,7 @@ def check_result(result) -> None:
else:
self.app.notify(
title="Success",
- message=f"Template created",
+ message=f"Template edited",
severity="information",
timeout=3,
)
diff --git a/src/bagels/components/modules/welcome.py b/src/bagels/components/modules/welcome.py
index 338cfb0..c32d4ef 100644
--- a/src/bagels/components/modules/welcome.py
+++ b/src/bagels/components/modules/welcome.py
@@ -17,7 +17,10 @@ def __init__(self, *args, **kwargs) -> None:
self.welcome_text = file.read()
def on_mount(self) -> None:
- self.set_interval(1 / 10, self.update_bagel)
+ if not self.app.is_testing:
+ self.set_interval(1 / 10, self.update_bagel)
+ else:
+ self.update_bagel()
def update_bagel(self) -> None:
bagel = self.query_one("#bagel")
diff --git a/src/bagels/components/percentage_bar.py b/src/bagels/components/percentage_bar.py
index 828e42a..09aaf33 100644
--- a/src/bagels/components/percentage_bar.py
+++ b/src/bagels/components/percentage_bar.py
@@ -137,8 +137,8 @@ def rebuild(self) -> None:
width = "1fr"
item_widget.styles.width = width
- item_widget.styles.background = background_color
if self.rounded:
+ item_widget.background = background_color
if i > 0:
prev_background_color = Color.from_rich_color(
RichColor.parse(self.items[i - 1].color)
@@ -146,6 +146,11 @@ def rebuild(self) -> None:
item_widget.update(
f"[{prev_background_color} on {background_color}][/{prev_background_color} on {background_color}]"
)
+ else:
+ item_widget.styles.hatch = (
+ "/",
+ background_color,
+ )
self.bar.mount(item_widget)
diff --git a/src/bagels/config.py b/src/bagels/config.py
index d57b4fc..8fb047a 100644
--- a/src/bagels/config.py
+++ b/src/bagels/config.py
@@ -11,6 +11,7 @@ class Defaults(BaseModel):
period: Literal["day", "week", "month", "year"] = "week"
first_day_of_week: int = Field(ge=0, le=6, default=6)
date_format: str = "%d/%m"
+ round_decimals: int = 2
class DatemodeHotkeys(BaseModel):
@@ -19,6 +20,7 @@ class DatemodeHotkeys(BaseModel):
class HomeHotkeys(BaseModel):
categories: str = "c"
+ budgets: str = "b"
new_transfer: str = "t"
toggle_splits: str = "s"
display_by_date: str = "q"
diff --git a/src/bagels/forms/record_forms.py b/src/bagels/forms/record_forms.py
index aa559d4..fe733e1 100644
--- a/src/bagels/forms/record_forms.py
+++ b/src/bagels/forms/record_forms.py
@@ -4,6 +4,7 @@
from rich.text import Text
from bagels.components.autocomplete import Dropdown
+from bagels.config import CONFIG
from bagels.managers.accounts import get_all_accounts_with_balance
from bagels.managers.categories import get_all_categories_by_freq
from bagels.managers.persons import create_person, get_all_persons
@@ -198,7 +199,10 @@ def get_filled_form(self, recordId: int) -> tuple[list, list]:
match fieldKey:
case "amount":
field.default_value = str(
- round(value - get_record_total_split_amount(recordId), 2)
+ round(
+ value - get_record_total_split_amount(recordId),
+ CONFIG.defaults.round_decimals,
+ )
)
case "date":
# if value is this month, simply set %d, else set %d %m %y
diff --git a/src/bagels/index.tcss b/src/bagels/index.tcss
index 9f7b83f..564c044 100644
--- a/src/bagels/index.tcss
+++ b/src/bagels/index.tcss
@@ -535,11 +535,6 @@ Button {
height: auto;
width: 1fr;
padding: 0 2;
-
- &.v {
- layout: vertical;
-
- }
&.h {
layout: horizontal;
@@ -552,7 +547,10 @@ Button {
width: 60%
}
}
-
+
+ &.v {
+ layout: vertical;
+ }
}
.module-container {
diff --git a/src/bagels/managers/accounts.py b/src/bagels/managers/accounts.py
index 62ee6ca..51ec78d 100644
--- a/src/bagels/managers/accounts.py
+++ b/src/bagels/managers/accounts.py
@@ -2,6 +2,7 @@
from sqlalchemy import select
from sqlalchemy.orm import sessionmaker
+from bagels.config import CONFIG
from bagels.models.account import Account
from bagels.models.record import Record
from bagels.models.split import Split
@@ -74,7 +75,7 @@ def get_account_balance(accountId, session=None):
else:
balance += split.amount
- return round(balance, 2)
+ return round(balance, CONFIG.defaults.round_decimals)
finally:
if should_close:
session.close()
diff --git a/src/bagels/managers/utils.py b/src/bagels/managers/utils.py
index 56e17ef..cee192a 100644
--- a/src/bagels/managers/utils.py
+++ b/src/bagels/managers/utils.py
@@ -75,7 +75,9 @@ def get_start_end_of_period(offset: int = 0, offset_type: str = "month"):
# -------------- figure -------------- #
-def get_period_figures(accountId=None, offset_type=None, offset=None, isIncome=None, session=None):
+def get_period_figures(
+ accountId=None, offset_type=None, offset=None, isIncome=None, session=None
+):
"""Returns the income / expense for a given period.
Rules:
@@ -142,7 +144,7 @@ def get_period_figures(accountId=None, offset_type=None, offset=None, isIncome=N
else:
total -= record_amount
- return abs(round(total, 2))
+ return abs(round(total, CONFIG.defaults.round_decimals))
finally:
if should_close:
session.close()
@@ -160,4 +162,4 @@ def _get_days_in_period(offset: int = 0, offset_type: str = "month"):
def get_period_average(net: int = 0, offset: int = 0, offset_type: str = "month"):
days = _get_days_in_period(offset, offset_type)
- return round(net / days, 2)
+ return round(net / days, CONFIG.defaults.round_decimals)
diff --git a/src/bagels/models/budget.py b/src/bagels/models/budget.py
new file mode 100644
index 0000000..4bd7c3a
--- /dev/null
+++ b/src/bagels/models/budget.py
@@ -0,0 +1,17 @@
+from datetime import datetime
+from sqlalchemy import Column, DateTime, Integer, String, Float, Boolean
+from sqlalchemy.orm import relationship
+from .database.db import Base
+
+
+class Budget(Base):
+ __tablename__ = "budget"
+
+ createdAt = Column(DateTime, nullable=False, default=datetime.now)
+ updatedAt = Column(
+ DateTime, nullable=False, default=datetime.now, onupdate=datetime.now
+ )
+
+ id = Column(Integer, primary_key=True, index=True)
+ name = Column(String, nullable=False)
+ amount = Column(Float, nullable=False)
diff --git a/src/bagels/models/database/app.py b/src/bagels/models/database/app.py
index bdc3e3a..738a856 100644
--- a/src/bagels/models/database/app.py
+++ b/src/bagels/models/database/app.py
@@ -14,6 +14,7 @@
from bagels.models.record import Record
from bagels.models.record_template import RecordTemplate
from bagels.models.split import Split
+from bagels.models.budget import Budget
from bagels.models.database.db import Base
diff --git a/src/bagels/textualrun.py b/src/bagels/textualrun.py
index 1e46bb8..cf4790f 100644
--- a/src/bagels/textualrun.py
+++ b/src/bagels/textualrun.py
@@ -4,7 +4,11 @@
from bagels.locations import set_custom_root
if __name__ == "__main__":
- # set_custom_root(Path("./instance/"))
+ set_custom_root(Path("../../../bagels_instance/"))
+
+ from bagels.config import load_config
+
+ load_config()
from bagels.models.database.app import init_db
diff --git a/tests/__snapshots__/snapshot/TestAccounts.test_5_acc_screen.svg b/tests/__snapshots__/snapshot/TestAccounts.test_5_acc_screen.svg
new file mode 100644
index 0000000..3bbdb9f
--- /dev/null
+++ b/tests/__snapshots__/snapshot/TestAccounts.test_5_acc_screen.svg
@@ -0,0 +1,204 @@
+
diff --git a/tests/__snapshots__/snapshot/TestAccounts.test_6_new_acc_home.svg b/tests/__snapshots__/snapshot/TestAccounts.test_6_new_acc_home.svg
new file mode 100644
index 0000000..f938221
--- /dev/null
+++ b/tests/__snapshots__/snapshot/TestAccounts.test_6_new_acc_home.svg
@@ -0,0 +1,201 @@
+
diff --git a/tests/__snapshots__/snapshot/TestAccounts.test_7_delete_acc.svg b/tests/__snapshots__/snapshot/TestAccounts.test_7_delete_acc.svg
new file mode 100644
index 0000000..608f37e
--- /dev/null
+++ b/tests/__snapshots__/snapshot/TestAccounts.test_7_delete_acc.svg
@@ -0,0 +1,201 @@
+
diff --git a/tests/__snapshots__/snapshot/TestAccounts.test_8_edit_acc.svg b/tests/__snapshots__/snapshot/TestAccounts.test_8_edit_acc.svg
new file mode 100644
index 0000000..2047a2e
--- /dev/null
+++ b/tests/__snapshots__/snapshot/TestAccounts.test_8_edit_acc.svg
@@ -0,0 +1,201 @@
+
diff --git a/tests/__snapshots__/snapshot/TestBasic.test_1_welcome.svg b/tests/__snapshots__/snapshot/TestBasic.test_1_welcome.svg
new file mode 100644
index 0000000..1acec7e
--- /dev/null
+++ b/tests/__snapshots__/snapshot/TestBasic.test_1_welcome.svg
@@ -0,0 +1,199 @@
+
diff --git a/tests/__snapshots__/snapshot/TestBasic.test_2_new_acc_welcome.svg b/tests/__snapshots__/snapshot/TestBasic.test_2_new_acc_welcome.svg
new file mode 100644
index 0000000..9e9ef21
--- /dev/null
+++ b/tests/__snapshots__/snapshot/TestBasic.test_2_new_acc_welcome.svg
@@ -0,0 +1,201 @@
+
diff --git a/tests/__snapshots__/snapshot/TestBasic.test_3_vertical_layout.svg b/tests/__snapshots__/snapshot/TestBasic.test_3_vertical_layout.svg
new file mode 100644
index 0000000..0c2cb79
--- /dev/null
+++ b/tests/__snapshots__/snapshot/TestBasic.test_3_vertical_layout.svg
@@ -0,0 +1,277 @@
+
diff --git a/tests/__snapshots__/snapshot/TestBasic.test_4_jump_screen.svg b/tests/__snapshots__/snapshot/TestBasic.test_4_jump_screen.svg
new file mode 100644
index 0000000..84b942f
--- /dev/null
+++ b/tests/__snapshots__/snapshot/TestBasic.test_4_jump_screen.svg
@@ -0,0 +1,203 @@
+
diff --git a/tests/__snapshots__/snapshot/TestCategories.test_10_new_category_screen.svg b/tests/__snapshots__/snapshot/TestCategories.test_10_new_category_screen.svg
new file mode 100644
index 0000000..59c605d
--- /dev/null
+++ b/tests/__snapshots__/snapshot/TestCategories.test_10_new_category_screen.svg
@@ -0,0 +1,210 @@
+
diff --git a/tests/__snapshots__/snapshot/TestCategories.test_11_new_category.svg b/tests/__snapshots__/snapshot/TestCategories.test_11_new_category.svg
new file mode 100644
index 0000000..27fdd67
--- /dev/null
+++ b/tests/__snapshots__/snapshot/TestCategories.test_11_new_category.svg
@@ -0,0 +1,212 @@
+
diff --git a/tests/__snapshots__/snapshot/TestCategories.test_12_new_category_subcategory.svg b/tests/__snapshots__/snapshot/TestCategories.test_12_new_category_subcategory.svg
new file mode 100644
index 0000000..36ac783
--- /dev/null
+++ b/tests/__snapshots__/snapshot/TestCategories.test_12_new_category_subcategory.svg
@@ -0,0 +1,212 @@
+
diff --git a/tests/__snapshots__/snapshot/TestCategories.test_13_delete_category.svg b/tests/__snapshots__/snapshot/TestCategories.test_13_delete_category.svg
new file mode 100644
index 0000000..f3087d7
--- /dev/null
+++ b/tests/__snapshots__/snapshot/TestCategories.test_13_delete_category.svg
@@ -0,0 +1,210 @@
+
diff --git a/tests/__snapshots__/snapshot/TestCategories.test_9_categories.svg b/tests/__snapshots__/snapshot/TestCategories.test_9_categories.svg
new file mode 100644
index 0000000..c7d1689
--- /dev/null
+++ b/tests/__snapshots__/snapshot/TestCategories.test_9_categories.svg
@@ -0,0 +1,210 @@
+
diff --git a/tests/__snapshots__/snapshot/TestTemplates.test_14_templates.svg b/tests/__snapshots__/snapshot/TestTemplates.test_14_templates.svg
new file mode 100644
index 0000000..fbbd7dc
--- /dev/null
+++ b/tests/__snapshots__/snapshot/TestTemplates.test_14_templates.svg
@@ -0,0 +1,202 @@
+
diff --git a/tests/__snapshots__/snapshot/TestTemplates.test_15_edit_template.svg b/tests/__snapshots__/snapshot/TestTemplates.test_15_edit_template.svg
new file mode 100644
index 0000000..e170a70
--- /dev/null
+++ b/tests/__snapshots__/snapshot/TestTemplates.test_15_edit_template.svg
@@ -0,0 +1,202 @@
+
diff --git a/tests/__snapshots__/snapshot/test_loads.svg b/tests/__snapshots__/snapshot/test_loads.svg
deleted file mode 100644
index c254c30..0000000
--- a/tests/__snapshots__/snapshot/test_loads.svg
+++ /dev/null
@@ -1,197 +0,0 @@
-
diff --git a/tests/snapshot.py b/tests/snapshot.py
index 2d62f85..486f337 100644
--- a/tests/snapshot.py
+++ b/tests/snapshot.py
@@ -1,12 +1,213 @@
+import time
import pytest
+
+import os
+import time_machine
+import datetime as dt
+from zoneinfo import ZoneInfo
+
from textual.pilot import Pilot
+hill_valley_tz = ZoneInfo("America/Los_Angeles")
+
+TEMP_INSTANCE_PATH = os.path.join(os.path.dirname(__file__), "../instance/")
+SIZE_BASIC = (140, 30)
+SIZE_VERTICAL = (70, 50)
+SIZE_TEST = (140, 30)
+APP_PARAMS = {"is_testing": True}
+
+
+# ---------- Clear last test --------- #
+
+if os.path.exists(TEMP_INSTANCE_PATH):
+ for file in os.listdir(TEMP_INSTANCE_PATH):
+ os.remove(os.path.join(TEMP_INSTANCE_PATH, file))
+
+# --------- Init app sequence -------- #
from bagels.locations import set_custom_root
-APP_PATH = "../src/bagels/app.py"
+set_custom_root(TEMP_INSTANCE_PATH)
+
+from bagels.config import load_config
+
+load_config()
+
+from bagels.models.database.app import init_db
+
+init_db()
+
+from bagels.app import App
+
+# -------------- Freeze -------------- #
+
+
+@pytest.fixture(autouse=True)
+def travel_to_hill_valley_time():
+ with time_machine.travel(dt.datetime(1985, 10, 26, 1, 24, tzinfo=hill_valley_tz)):
+ yield # This allows the tests to run within the context of the travel
+
+
+# --------------- Basic -------------- #
+
+
+class TestBasic:
+ def test_1_welcome(self, snap_compare):
+ assert snap_compare(App(**APP_PARAMS), terminal_size=SIZE_BASIC)
+
+ def test_2_new_acc_welcome(self, snap_compare):
+ async def r(pilot: Pilot):
+ await pilot.press("a") # add
+ await pilot.press("t", "tab") # name
+ await pilot.press(*"123.45", "enter") # value
+
+ assert snap_compare(App(**APP_PARAMS), terminal_size=SIZE_BASIC, run_before=r)
+
+ def test_3_vertical_layout(self, snap_compare):
+ assert snap_compare(
+ App(**APP_PARAMS),
+ terminal_size=SIZE_VERTICAL,
+ )
+
+ def test_4_jump_screen(self, snap_compare):
+ assert snap_compare(
+ App(**APP_PARAMS),
+ terminal_size=SIZE_BASIC,
+ press=["v"],
+ )
+
+
+# ------------- Accounts ------------- #
+
+
+class TestAccounts:
+ def test_5_acc_screen(self, snap_compare):
+ assert snap_compare(App(**APP_PARAMS), terminal_size=SIZE_TEST, press=["a"])
+
+ def test_6_new_acc_home(self, snap_compare):
+ async def r(pilot: Pilot):
+ await pilot.press("v", "a") # jump to accounts
+ await pilot.wait_for_animation()
+ await pilot.press("a") # add
+ await pilot.press(*"t2", "enter") # name
+
+ assert snap_compare(
+ App(**APP_PARAMS),
+ terminal_size=SIZE_TEST,
+ run_before=r,
+ )
+
+ def test_7_delete_acc(self, snap_compare):
+ async def r(pilot: Pilot):
+ await pilot.press("v", "a") # jump to accounts
+ await pilot.press("d", "enter") # delete
+
+ assert snap_compare(
+ App(**APP_PARAMS),
+ terminal_size=SIZE_TEST,
+ run_before=r,
+ )
+
+ def test_8_edit_acc(self, snap_compare):
+ async def r(pilot: Pilot):
+ await pilot.press("v", "a") # jump to accounts
+ await pilot.press("e") # edit
+ await pilot.press("tab", *"123.234", "enter") # edit beg. balance
+
+ assert snap_compare(
+ App(**APP_PARAMS),
+ terminal_size=SIZE_TEST,
+ run_before=r,
+ )
+
+
+# ------------ Categories ------------ #
+
+
+class TestCategories:
+ def test_9_categories(self, snap_compare):
+ assert snap_compare(
+ App(**APP_PARAMS),
+ terminal_size=SIZE_TEST,
+ press=["c"],
+ )
+
+ def test_10_new_category_screen(self, snap_compare):
+ assert snap_compare(
+ App(**APP_PARAMS),
+ terminal_size=SIZE_TEST,
+ press=["c", "a"],
+ )
+
+ def test_11_new_category(self, snap_compare):
+ async def r(pilot: Pilot):
+ await pilot.press("c") # show categories modal
+ await pilot.press("a") # add
+ await pilot.press("t", "tab") # name
+ await pilot.press("tab", "tab", "enter") # nature, color, save
+
+ # todo: scroll to bottom
+ assert snap_compare(
+ App(**APP_PARAMS),
+ terminal_size=SIZE_TEST,
+ run_before=r,
+ )
+
+ def test_12_new_category_subcategory(self, snap_compare):
+ async def r(pilot: Pilot):
+ await pilot.press("c") # show categories modal
+ await pilot.press("up") # select last created
+ await pilot.press("s") # add subcategory
+ await pilot.press("t", "tab") # name
+ await pilot.press("tab", "tab", "enter") # nature, color, save
+
+ assert snap_compare(
+ App(**APP_PARAMS),
+ terminal_size=SIZE_TEST,
+ run_before=r,
+ )
+
+ def test_13_delete_category(self, snap_compare):
+ async def r(pilot: Pilot):
+ await pilot.press("c") # show categories modal
+ await pilot.press("d", "enter") # delete
+
+ assert snap_compare(
+ App(**APP_PARAMS),
+ terminal_size=SIZE_TEST,
+ run_before=r,
+ )
+
+
+# ------------- Templates ------------ #
+
+
+class TestTemplates:
+ def test_14_templates(self, snap_compare):
+ async def r(pilot: Pilot):
+ await pilot.press("v", "t") # jump to templates
+ await pilot.press("a") # add
+ await pilot.press("t", "tab") # name
+ await pilot.press("tab") # category
+ await pilot.press(*"123") # enter value
+ await pilot.press("tab") # account
+ await pilot.press("tab", "space", "enter") # switch to income, save
-set_custom_root("./instance/")
+ assert snap_compare(
+ App(**APP_PARAMS),
+ terminal_size=SIZE_TEST,
+ run_before=r,
+ )
+ def test_15_edit_template(self, snap_compare):
+ async def r(pilot: Pilot):
+ await pilot.press("v", "t") # jump to templates
+ await pilot.press("tab") # select first template
+ await pilot.press("e") # edit
+ await pilot.press(*"est") # edit name
+ await pilot.press("enter") # save
-def test_loads(snap_compare):
- assert snap_compare(APP_PATH, terminal_size=(140, 40))
+ assert snap_compare(
+ App(**APP_PARAMS),
+ terminal_size=SIZE_TEST,
+ run_before=r,
+ )
diff --git a/uv.lock b/uv.lock
index f08d507..5c6697c 100644
--- a/uv.lock
+++ b/uv.lock
@@ -98,7 +98,6 @@ dependencies = [
{ name = "blinker" },
{ name = "click" },
{ name = "click-default-group" },
- { name = "freezegun" },
{ name = "frozenlist" },
{ name = "idna" },
{ name = "itsdangerous" },
@@ -138,6 +137,7 @@ dev = [
{ name = "pytest-xdist" },
{ name = "syrupy" },
{ name = "textual-dev" },
+ { name = "time-machine" },
]
[package.metadata]
@@ -151,7 +151,6 @@ requires-dist = [
{ name = "blinker", specifier = "==1.8.2" },
{ name = "click", specifier = "==8.1.7" },
{ name = "click-default-group", specifier = ">=1.2.4" },
- { name = "freezegun", specifier = ">=1.5.1" },
{ name = "frozenlist", specifier = "==1.5.0" },
{ name = "idna", specifier = "==3.10" },
{ name = "itsdangerous", specifier = "==2.2.0" },
@@ -191,6 +190,7 @@ dev = [
{ name = "pytest-xdist", specifier = ">=3.6.1" },
{ name = "syrupy", specifier = ">=4.6.1" },
{ name = "textual-dev", specifier = "==1.6.1" },
+ { name = "time-machine", specifier = "==2.16.0" },
]
[[package]]
@@ -272,18 +272,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size = 40612 },
]
-[[package]]
-name = "freezegun"
-version = "1.5.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "python-dateutil" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/2c/ef/722b8d71ddf4d48f25f6d78aa2533d505bf3eec000a7cacb8ccc8de61f2f/freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9", size = 33697 }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/51/0b/0d7fee5919bccc1fdc1c2a7528b98f65c6f69b223a3fd8f809918c142c36/freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1", size = 17569 },
-]
-
[[package]]
name = "frozenlist"
version = "1.5.0"
@@ -820,6 +808,28 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/07/a9/01d35770fde8d889e1fe28b726188cf28801e57afd369c614cd2bc100ee4/textual_serve-1.1.1-py3-none-any.whl", hash = "sha256:568782f1c0e60e3f7039d9121e1cb5c2f4ca1aaf6d6bd7aeb833d5763a534cb2", size = 445034 },
]
+[[package]]
+name = "time-machine"
+version = "2.16.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "python-dateutil" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/fb/dd/5022939b9cadefe3af04f4012186c29b8afbe858b1ec2cfa38baeec94dab/time_machine-2.16.0.tar.gz", hash = "sha256:4a99acc273d2f98add23a89b94d4dd9e14969c01214c8514bfa78e4e9364c7e2", size = 24626 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a6/18/3087d0eb185cedbc82385f46bf16032ec7102a0e070205a2c88c4ecf9952/time_machine-2.16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7751bf745d54e9e8b358c0afa332815da9b8a6194b26d0fd62876ab6c4d5c9c0", size = 20209 },
+ { url = "https://files.pythonhosted.org/packages/03/a3/fcc3eaf69390402ecf491d718e533b6d0e06d944d77fc8d87be3a2839102/time_machine-2.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1784edf173ca840ba154de6eed000b5727f65ab92972c2f88cec5c4d6349c5f2", size = 16681 },
+ { url = "https://files.pythonhosted.org/packages/a2/96/8b76d264014bf9dc21873218de50d67223c71736f87fe6c65e582f7c29ac/time_machine-2.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f5876a5682ce1f517e55d7ace2383432627889f6f7e338b961f99d684fd9e8d", size = 33768 },
+ { url = "https://files.pythonhosted.org/packages/5c/13/59ae8259be02b6c657ef6e3b6952bf274b43849f6f35cc61a576c68ce301/time_machine-2.16.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:806672529a2e255cd901f244c9033767dc1fa53466d0d3e3e49565a1572a64fe", size = 31685 },
+ { url = "https://files.pythonhosted.org/packages/3e/c1/9f142beb4d373a2a01ebb58d5117289315baa5131d880ec804db49e94bf7/time_machine-2.16.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:667b150fedb54acdca2a4bea5bf6da837b43e6dd12857301b48191f8803ba93f", size = 33447 },
+ { url = "https://files.pythonhosted.org/packages/95/f7/ed9ecd93c2d38dca77d0a28e070020f3ce0fb23e0d4a6edb14bcfffa5526/time_machine-2.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:da3ae1028af240c0c46c79adf9c1acffecc6ed1701f2863b8132f5ceae6ae4b5", size = 33408 },
+ { url = "https://files.pythonhosted.org/packages/91/40/d0d274d70fa2c4cad531745deb8c81346365beb0a2736be05a3acde8b94a/time_machine-2.16.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:520a814ea1b2706c89ab260a54023033d3015abef25c77873b83e3d7c1fafbb2", size = 31526 },
+ { url = "https://files.pythonhosted.org/packages/1d/ba/a27cdbb324d9a6d779cde0d514d47b696b5a6a653705d4b511fd65ef1514/time_machine-2.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8243664438bb468408b29c6865958662d75e51f79c91842d2794fa22629eb697", size = 33042 },
+ { url = "https://files.pythonhosted.org/packages/72/63/64e9156c9e38c18720d0cc41378168635241de44013ffe3dd5b099447eb0/time_machine-2.16.0-cp313-cp313-win32.whl", hash = "sha256:32d445ce20d25c60ab92153c073942b0bac9815bfbfd152ce3dcc225d15ce988", size = 19108 },
+ { url = "https://files.pythonhosted.org/packages/3d/40/27f5738fbd50b78dcc0682c14417eac5a49ccf430525dd0c5a058be125a2/time_machine-2.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:f6927dda86425f97ffda36131f297b1a601c64a6ee6838bfa0e6d3149c2f0d9f", size = 19935 },
+ { url = "https://files.pythonhosted.org/packages/35/75/c4d8b2f0fe7dac22854d88a9c509d428e78ac4bf284bc54cfe83f75cc13b/time_machine-2.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:4d3843143c46dddca6491a954bbd0abfd435681512ac343169560e9bab504129", size = 18047 },
+]
+
[[package]]
name = "tomli"
version = "2.0.2"