diff --git a/src/replica.rs b/src/replica.rs index 810bdf9..1d2cd2f 100644 --- a/src/replica.rs +++ b/src/replica.rs @@ -78,7 +78,7 @@ impl Replica { } pub fn working_set(&mut self) -> anyhow::Result { - Ok(self.0.working_set().map(WorkingSet)?) + Ok(self.0.working_set().map(WorkingSet::from)?) } pub fn dependency_map(&mut self, force: bool) -> anyhow::Result { diff --git a/src/working_set.rs b/src/working_set.rs index eccef6a..ceba6ed 100644 --- a/src/working_set.rs +++ b/src/working_set.rs @@ -1,9 +1,11 @@ +use std::fmt; + use pyo3::prelude::*; use taskchampion::Uuid; use taskchampion::WorkingSet as TCWorkingSet; -// TODO: convert working set into python's iterable type + #[pyclass] -pub struct WorkingSet(pub(crate) TCWorkingSet); +pub struct WorkingSet(TCWorkingSet); #[pyclass] struct WorkingSetIter { @@ -26,6 +28,10 @@ impl WorkingSet { self.0.len() } + pub fn __repr__(&self) -> String { + format!("{:?}", self) + } + pub fn largest_index(&self) -> usize { self.0.largest_index() } @@ -39,7 +45,6 @@ impl WorkingSet { } pub fn by_uuid(&self, uuid: String) -> Option { - // TODO I don't like the conversion, should use try-expect or something else as an input self.0.by_uuid(Uuid::parse_str(&uuid).unwrap()) } @@ -55,3 +60,31 @@ impl WorkingSet { Py::new(slf.py(), iter) } } + +// TODO: Use the Taskchampion Debug implementation when +// https://github.com/GothenburgBitFactory/taskchampion/pull/520 is available. +impl fmt::Debug for WorkingSet { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("WorkingSet {")?; + f.debug_list().entries(self.0.iter()).finish()?; + f.write_str("}") + } +} + +impl AsRef for WorkingSet { + fn as_ref(&self) -> &TCWorkingSet { + &self.0 + } +} + +impl From for WorkingSet { + fn from(value: TCWorkingSet) -> Self { + WorkingSet(value) + } +} + +impl From for TCWorkingSet { + fn from(value: WorkingSet) -> Self { + value.0 + } +} diff --git a/taskchampion.pyi b/taskchampion.pyi index 766cc80..edf8bfa 100644 --- a/taskchampion.pyi +++ b/taskchampion.pyi @@ -131,7 +131,6 @@ class WorkingSet: def by_index(self, index: int) -> Optional[str]: ... def by_uuid(self, uuid: str) -> Optional[int]: ... def __iter__(self) -> Iterator[tuple[int, str]]: ... - def __next__(self) -> tuple[int, str]: ... class Annotation: entry: datetime diff --git a/tests/test_replica.py b/tests/test_replica.py index 4a36301..7f1b5f3 100644 --- a/tests/test_replica.py +++ b/tests/test_replica.py @@ -85,15 +85,6 @@ def test_all_tasks(empty_replica: Replica): assert tasks[key] != 0 -def test_working_set(replica_with_tasks: Replica): - ws = replica_with_tasks.working_set() - - assert ws is not None - - -# TODO: create testable and inspectable WorkingSet - - def test_get_task(replica_with_tasks: Replica): uuid = replica_with_tasks.all_task_uuids()[0] @@ -102,17 +93,6 @@ def test_get_task(replica_with_tasks: Replica): assert task is not None -@pytest.mark.skip() -def test_rebuild_working_set(replica_with_tasks: Replica): - # TODO actually test this - replica_with_tasks.rebuild_working_set(False) - - -@pytest.mark.skip() -def test_add_undo_point(replica_with_tasks: Replica): - replica_with_tasks.add_undo_point(False) - - def test_num_local_operations(replica_with_tasks: Replica): assert replica_with_tasks.num_local_operations() == 3 diff --git a/tests/test_working_set.py b/tests/test_working_set.py index 5769b80..cdd6849 100644 --- a/tests/test_working_set.py +++ b/tests/test_working_set.py @@ -1,48 +1,71 @@ from taskchampion import Replica, WorkingSet, Status, Operations from pathlib import Path import pytest +import re import uuid @pytest.fixture -def working_set(): +def uuids(): + return [str(uuid.uuid4()) for _ in range(0, 5)] + + +@pytest.fixture +def working_set(uuids: list[str]): r = Replica.new_in_memory() ops = Operations() - task = r.create_task(str(uuid.uuid4()), ops) - task.set_status(Status.Pending, ops) - task = r.create_task(str(uuid.uuid4()), ops) - task.set_status(Status.Pending, ops) - task.start(ops) + task1 = r.create_task(uuids[1], ops) + task1.set_status(Status.Pending, ops) + task2 = r.create_task(uuids[2], ops) + task2.set_status(Status.Pending, ops) + task2.start(ops) + task3 = r.create_task(uuids[3], ops) + task3.set_status(Status.Pending, ops) + task4 = r.create_task(uuids[4], ops) + task4.set_status(Status.Pending, ops) r.commit_operations(ops) + # Remove task 3 from working set + ops = Operations() + task3.set_status(Status.Completed, ops) + r.commit_operations(ops) + r.rebuild_working_set(False) + return r.working_set() def test_len(working_set: WorkingSet): - assert len(working_set) == 2 + assert len(working_set) == 3 + + +def test_repr(working_set: WorkingSet): + # The Rust Debug output contains lots of internal details that we do not + # need to check for here. + assert re.match(r"^WorkingSet {.*}$", repr(working_set)) def test_largest_index(working_set: WorkingSet): - assert working_set.largest_index() == 2 + assert working_set.largest_index() == 4 def test_is_empty(working_set: WorkingSet): assert not working_set.is_empty() -def test_by_index(working_set: WorkingSet): - assert working_set.by_index(1) is not None - +def test_by_index(working_set: WorkingSet, uuids: list[str]): + assert working_set.by_index(1) == uuids[1] + assert working_set.by_index(2) == uuids[2] + assert working_set.by_index(3) == None + assert working_set.by_index(4) == uuids[4] -def test_iter(working_set: WorkingSet): - assert iter(working_set) +def test_by_uuid(working_set: WorkingSet, uuids: list[str]): + assert working_set.by_uuid(uuids[1]) == 1 + assert working_set.by_uuid(uuids[2]) == 2 + assert working_set.by_uuid(uuids[3]) == None + assert working_set.by_uuid(uuids[4]) == 4 -def test_next(working_set: WorkingSet): - working_set_iterator = iter(working_set) - assert next(working_set_iterator)[0] == 1 - assert next(working_set_iterator)[0] == 2 - with pytest.raises(StopIteration): - next(working_set_iterator) +def test_iter(working_set: WorkingSet, uuids: list[str]): + assert list(working_set) == [(1, uuids[1]), (2, uuids[2]), (4, uuids[4])]