diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..88fe65b --- /dev/null +++ b/conftest.py @@ -0,0 +1,11 @@ +from pytest import Config + + +def pytest_configure(config: Config): + """ + Registers a new mark 'final' that can be applied to tests. + + I did not want to add these in pyproject.toml. + """ + + config.addinivalue_line("markers", "final: the final test") diff --git a/justfile b/justfile index 9aec35a..3d77840 100644 --- a/justfile +++ b/justfile @@ -41,6 +41,9 @@ update-deps: # Run tests, ARGS are passed-through to pytest test *ARGS: + uv run pytest tests.py -m "not final" {{ ARGS }} + +alltests *ARGS: uv run pytest tests.py {{ ARGS }} # run tests and create coverage reports diff --git a/mise.toml b/mise.toml index ffbd3a8..ca3c768 100644 --- a/mise.toml +++ b/mise.toml @@ -3,6 +3,7 @@ DATABASE_URL = "sqlite:///database.db" [tools] +difftastic = "latest" markdownlint-cli2 = "latest" prek = "latest" python = "3.14" diff --git a/pyproject.toml b/pyproject.toml index d4c43ed..c0b2f35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,6 +64,7 @@ omit = ["tests.py"] source_dirs = ["src/"] [tool.pytest] +# custom marks are created in conftest.py asyncio_mode = "auto" [tool.ruff] diff --git a/src/teilchensammler_cli/models.py b/src/teilchensammler_cli/models.py index f72650e..77619a8 100644 --- a/src/teilchensammler_cli/models.py +++ b/src/teilchensammler_cli/models.py @@ -21,7 +21,7 @@ class TeilchenCreate(SQLModel): class Teilchen(TeilchenCreate, table=True): - id: uuid.UUID = Field(default_factory=uuid.uuid7, primary_key=True) # ty:ignore[unresolved-attribute] + id: uuid.UUID = Field(default_factory=uuid.uuid7, primary_key=True) async def make_teilchen_input(text: str) -> TeilchenCreate | None: @@ -59,7 +59,7 @@ async def make_teilchen_input(text: str) -> TeilchenCreate | None: description_start = text.find('"', name_end + 1) description_end = text.find('"', description_start + 1) if description_end > description_start: - description = text[description_start:description_end] + description = text[description_start + 1 : description_end] else: description = "" diff --git a/tests.py b/tests.py index 0b547c6..b4a3f08 100644 --- a/tests.py +++ b/tests.py @@ -1,4 +1,3 @@ -from typing import TypedDict import pytest import logging @@ -7,23 +6,14 @@ from teilchensammler_cli.models import TeilchenCreate, make_teilchen_input logger = logging.getLogger(__name__) +@pytest.mark.final # don't run while we are fiddling with the app def test_initial_layout(snap_compare): from teilchensammler_cli.main import app assert snap_compare(app, terminal_size=(130, 40)) -class Teilchen(TypedDict): - """The things I do for my LSP...""" - - name: str - description: str - tags: str - text: str - number: int - - -empty_teilchen: Teilchen = { +empty_teilchen = { "name": "", "description": "", "tags": "", @@ -32,23 +22,62 @@ empty_teilchen: Teilchen = { } +def TC(**kwargs) -> TeilchenCreate | None: + """TC = TeilchenCreate + + Create Teilchen with desired attributes. + + Args: + input: mapping that must be well-formed enough to actually create a Teilchen + + Returns: + - `None` on empty input + - an instance of `TeilchenCreate` + """ + if kwargs: + arguments = empty_teilchen | kwargs + return TeilchenCreate(**arguments) # ty:ignore[invalid-argument-type] + + @pytest.mark.parametrize( "example_input,expected", [ - ("", None), - ("a", None), - ("a.", {"name": "a", "text": "a."}), - ("aa", None), - ("aa.", {"name": "aa", "text": "aa."}), + # Not enough data + ("", TC()), + (".", TC()), + ("..", TC()), + (".a.", TC()), + ("a", TC()), + ("aa", TC()), + # Just enough for "name" + ("a.", TC(name="a", text="a.")), + ("aa.", TC(name="aa", text="aa.")), + ("a..", TC(name="a", text="a..")), + ("a.b.", TC(name="a", text="a.b.")), + # Just enough for "name" and "description" + ('a."b"', TC(name="a", description="b", text='a."b"')), + ('a. "b"', TC(name="a", description="b", text='a. "b"')), + ('a. ""', TC(name="a", description="", text='a. ""')), + ('. ""', TC()), + ('. "b"', TC()), + # Just enough for "name" and "description" and "tags" + ('a. "b" #c', TC(name="a", description="b", tags="#c", text='a. "b" #c')), + ('a. "b #d" #c', TC(name="a", description="b #d", tags="#c #d", text='a. "b #d" #c')), + # swap order in input, tag result is stable + ('a. "b #c" #d', TC(name="a", description="b #c", tags="#c #d", text='a. "b #c" #d')), + # Just enough for "name" and "tags" + ("a. #d", TC(name="a", description="", tags="#d", text="a. #d")), + ("a. #d#b", TC(name="a", description="", tags="#b #d", text="a. #d#b")), + ("a. ##b", TC(name="a", description="", tags="#b", text="a. ##b")), + ("a. #", TC(name="a", description="", tags="", text="a. #")), + ("a. ##", TC(name="a", description="", tags="", text="a. ##")), + # do we care about "number"? ], ) -async def test_teilchendata_must_include_period( +async def test_maketeilcheninput_can_create_desired_teilchen( example_input: str, expected: dict[str, str | int] | None ) -> None: - thing = expected - if expected: - arguments = empty_teilchen | expected - thing = TeilchenCreate(**arguments) # ty:ignore[invalid-argument-type] + actual = await make_teilchen_input(example_input) - assert await make_teilchen_input(example_input) == thing + assert expected == actual diff --git a/uv.lock b/uv.lock index d2b5c5a..9ece310 100644 --- a/uv.lock +++ b/uv.lock @@ -1161,7 +1161,6 @@ dependencies = [ { name = "orjson" }, { name = "sqlmodel" }, { name = "textual" }, - { name = "textual-dev" }, ] [package.dev-dependencies] @@ -1171,6 +1170,7 @@ dev = [ { name = "pytest-asyncio" }, { name = "pytest-cov" }, { name = "pytest-textual-snapshot" }, + { name = "textual-dev" }, { name = "twine" }, ] @@ -1181,7 +1181,6 @@ requires-dist = [ { name = "orjson", specifier = ">=3.11.4" }, { name = "sqlmodel", specifier = ">=0.0.27" }, { name = "textual", specifier = ">=6.7.1" }, - { name = "textual-dev", specifier = ">=1.8.0" }, ] [package.metadata.requires-dev] @@ -1191,6 +1190,7 @@ dev = [ { name = "pytest-asyncio", specifier = ">=1.3.0" }, { name = "pytest-cov", specifier = ">=7.0.0" }, { name = "pytest-textual-snapshot", specifier = ">=1.0.0" }, + { name = "textual-dev", specifier = ">=1.8.0" }, { name = "twine", specifier = ">=6.2.0" }, ]