aboutsummaryrefslogtreecommitdiff
path: root/pocketbook/tests
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-26 14:05:40 -0500
committerCraig Jennings <c@cjennings.net>2026-05-26 14:05:40 -0500
commit70e89e946cbdff307284d11a46558161f713607c (patch)
treec305ce9249fac0aef1318f5caabae0df31be7e95 /pocketbook/tests
parent92f4a9394ae1b662d037a3016e94058a3881bdb8 (diff)
downloadarchsetup-70e89e946cbdff307284d11a46558161f713607c.tar.gz
archsetup-70e89e946cbdff307284d11a46558161f713607c.zip
refactor: fold pocketbook in-tree and drop its install steps
Pocketbook is nowhere near ready, so I pulled it back from publication: deleted the github mirror and the cjennings.net repo, removed the server mirror hook, and copied the package into pocketbook/ here until it's ready to spin back out. Dropped the steps that provisioned it on a fresh install: the gtk4-layer-shell dep and the pip install in archsetup, and the clone in post-install.sh. That clone pointed at the now-deleted github repo, so it would have failed a fresh run regardless. Re-wiring the install is tracked in the pocketbook backlog.
Diffstat (limited to 'pocketbook/tests')
-rw-r--r--pocketbook/tests/__init__.py0
-rw-r--r--pocketbook/tests/conftest.py17
-rw-r--r--pocketbook/tests/test_app_toggle.py96
-rw-r--r--pocketbook/tests/test_note.py69
-rw-r--r--pocketbook/tests/test_panel.py40
-rw-r--r--pocketbook/tests/test_store.py103
6 files changed, 325 insertions, 0 deletions
diff --git a/pocketbook/tests/__init__.py b/pocketbook/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pocketbook/tests/__init__.py
diff --git a/pocketbook/tests/conftest.py b/pocketbook/tests/conftest.py
new file mode 100644
index 0000000..db04f88
--- /dev/null
+++ b/pocketbook/tests/conftest.py
@@ -0,0 +1,17 @@
+import pytest
+
+
+@pytest.fixture
+def notes_dir(tmp_path):
+ """Temporary directory for note storage."""
+ d = tmp_path / "quicknotes"
+ d.mkdir()
+ return d
+
+
+@pytest.fixture
+def sample_note_file(notes_dir):
+ """A sample note file on disk."""
+ path = notes_dir / "0001-20260101-120000-abc12.txt"
+ path.write_text("Shopping List\n\nMilk\nEggs\nBread\n")
+ return path
diff --git a/pocketbook/tests/test_app_toggle.py b/pocketbook/tests/test_app_toggle.py
new file mode 100644
index 0000000..cb5ab89
--- /dev/null
+++ b/pocketbook/tests/test_app_toggle.py
@@ -0,0 +1,96 @@
+from pocketbook.app import ToggleStateMachine, EscapeStateMachine, navigate
+
+
+class TestToggleStateMachine:
+ def test_initial_state_visible(self):
+ sm = ToggleStateMachine(start_hidden=False)
+ assert sm.visible is True
+
+ def test_initial_state_hidden(self):
+ sm = ToggleStateMachine(start_hidden=True)
+ assert sm.visible is False
+
+ def test_toggle_alternates(self):
+ sm = ToggleStateMachine(start_hidden=False)
+ assert sm.visible is True
+ sm.toggle()
+ assert sm.visible is False
+ sm.toggle()
+ assert sm.visible is True
+
+ def test_toggle_from_hidden(self):
+ sm = ToggleStateMachine(start_hidden=True)
+ assert sm.visible is False
+ sm.toggle()
+ assert sm.visible is True
+ sm.toggle()
+ assert sm.visible is False
+
+
+class TestEscapeStateMachine:
+ def test_escape_while_editing_returns_exit_edit(self):
+ esc = EscapeStateMachine()
+ toggle = ToggleStateMachine(start_hidden=False)
+ action = esc.escape(is_editing=True, toggle=toggle)
+ assert action == "exit_edit"
+ # Toggle state should not change
+ assert toggle.visible is True
+
+ def test_escape_while_browsing_returns_hide(self):
+ esc = EscapeStateMachine()
+ toggle = ToggleStateMachine(start_hidden=False)
+ action = esc.escape(is_editing=False, toggle=toggle)
+ assert action == "hide"
+ assert toggle.visible is False
+
+ def test_escape_edit_then_browse_hides(self):
+ """Simulates: editing → Escape (exit edit) → Escape (hide)."""
+ esc = EscapeStateMachine()
+ toggle = ToggleStateMachine(start_hidden=False)
+
+ # First escape: exit edit mode
+ action1 = esc.escape(is_editing=True, toggle=toggle)
+ assert action1 == "exit_edit"
+ assert toggle.visible is True
+
+ # Second escape: now in browse mode, hide panel
+ action2 = esc.escape(is_editing=False, toggle=toggle)
+ assert action2 == "hide"
+ assert toggle.visible is False
+
+ def test_escape_hide_does_not_change_when_already_hidden(self):
+ esc = EscapeStateMachine()
+ toggle = ToggleStateMachine(start_hidden=True)
+ assert toggle.visible is False
+ action = esc.escape(is_editing=False, toggle=toggle)
+ assert action == "hide"
+ # Toggled again — now visible (edge case if called when hidden)
+ assert toggle.visible is True
+
+
+class TestNavigate:
+ def test_no_notes(self):
+ assert navigate(None, 0, 1) is None
+ assert navigate(None, 0, -1) is None
+
+ def test_no_focus_next_goes_to_first(self):
+ assert navigate(None, 3, 1) == 0
+
+ def test_no_focus_prev_goes_to_last(self):
+ assert navigate(None, 3, -1) == 2
+
+ def test_next_from_middle(self):
+ assert navigate(1, 3, 1) == 2
+
+ def test_prev_from_middle(self):
+ assert navigate(1, 3, -1) == 0
+
+ def test_next_clamps_at_end(self):
+ assert navigate(2, 3, 1) == 2
+
+ def test_prev_clamps_at_start(self):
+ assert navigate(0, 3, -1) == 0
+
+ def test_single_note(self):
+ assert navigate(0, 1, 1) == 0
+ assert navigate(0, 1, -1) == 0
diff --git a/pocketbook/tests/test_note.py b/pocketbook/tests/test_note.py
new file mode 100644
index 0000000..539451a
--- /dev/null
+++ b/pocketbook/tests/test_note.py
@@ -0,0 +1,69 @@
+from pocketbook.note import Note
+
+
+class TestNoteSerialisation:
+ def test_round_trip(self):
+ note = Note(title="Shopping", body="Milk\nEggs\nBread")
+ content = note.to_file_content()
+ restored = Note.from_file_content(content)
+ assert restored.title == note.title
+ assert restored.body == note.body
+
+ def test_empty_body(self):
+ note = Note(title="Empty", body="")
+ content = note.to_file_content()
+ restored = Note.from_file_content(content)
+ assert restored.title == "Empty"
+ assert restored.body == ""
+
+ def test_empty_title(self):
+ note = Note(title="", body="some body")
+ content = note.to_file_content()
+ restored = Note.from_file_content(content)
+ assert restored.title == ""
+ assert restored.body == "some body"
+
+ def test_unicode(self):
+ note = Note(title="日本語タイトル", body="Ünïcödé bödý 🎉")
+ content = note.to_file_content()
+ restored = Note.from_file_content(content)
+ assert restored.title == "日本語タイトル"
+ assert restored.body == "Ünïcödé bödý 🎉"
+
+ def test_multiline_body(self):
+ body = "Line 1\nLine 2\n\nLine 4\n"
+ note = Note(title="Multi", body=body)
+ content = note.to_file_content()
+ restored = Note.from_file_content(content)
+ assert restored.body == body
+
+ def test_file_content_format(self):
+ """Title on line 1, blank line, then body."""
+ note = Note(title="Title", body="Body text")
+ content = note.to_file_content()
+ assert content == "Title\n\nBody text"
+
+ def test_from_file_content_no_blank_line(self):
+ """Gracefully handle files without a blank separator."""
+ restored = Note.from_file_content("JustTitle")
+ assert restored.title == "JustTitle"
+ assert restored.body == ""
+
+
+class TestNoteFilename:
+ def test_generate_filename(self):
+ note = Note(title="Test", body="")
+ filename = note.generate_filename(order=1)
+ assert filename.startswith("0001-")
+ assert filename.endswith(".txt")
+ # Format: 0001-YYYYMMDD-HHMMSS-shortid.txt
+ parts = filename.split("-")
+ assert len(parts) == 4
+ assert len(parts[0]) == 4 # order
+ assert len(parts[1]) == 8 # date
+ # parts[2] = HHMMSS + shortid.txt combined via split on -
+ # Actually: 0001-20260225-143012-abc12.txt has 4 parts
+
+ def test_parse_order_from_filename(self):
+ assert Note.parse_order("0005-20260101-120000-abc12.txt") == 5
+ assert Note.parse_order("0001-20260101-120000-xyz99.txt") == 1
diff --git a/pocketbook/tests/test_panel.py b/pocketbook/tests/test_panel.py
new file mode 100644
index 0000000..92f8648
--- /dev/null
+++ b/pocketbook/tests/test_panel.py
@@ -0,0 +1,40 @@
+from unittest.mock import MagicMock
+from pocketbook.note import Note
+
+
+class TestPanelController:
+ """Test panel controller logic with a mocked store."""
+
+ def _make_controller(self):
+ from pocketbook.panel import PanelController
+ store = MagicMock()
+ controller = PanelController(store)
+ return controller, store
+
+ def test_add_note_calls_store_create(self):
+ controller, store = self._make_controller()
+ store.create.return_value = "0001-20260101-120000-abc12.txt"
+ controller.add_note()
+ store.create.assert_called_once_with("New Note", "")
+
+ def test_delete_note_calls_store_delete(self):
+ controller, store = self._make_controller()
+ controller.delete_note("0001-20260101-120000-abc12.txt")
+ store.delete.assert_called_once_with("0001-20260101-120000-abc12.txt")
+
+ def test_update_note_calls_store_update(self):
+ controller, store = self._make_controller()
+ controller.update_note("0001-20260101-120000-abc12.txt", "New Title", "New Body")
+ store.update.assert_called_once_with(
+ "0001-20260101-120000-abc12.txt", "New Title", "New Body"
+ )
+
+ def test_get_notes_calls_store_list(self):
+ controller, store = self._make_controller()
+ store.list_notes.return_value = [
+ ("0001-20260101-120000-abc12.txt", Note(title="A", body="a")),
+ ]
+ notes = controller.get_notes()
+ store.list_notes.assert_called_once()
+ assert len(notes) == 1
+ assert notes[0][1].title == "A"
diff --git a/pocketbook/tests/test_store.py b/pocketbook/tests/test_store.py
new file mode 100644
index 0000000..fab5bd6
--- /dev/null
+++ b/pocketbook/tests/test_store.py
@@ -0,0 +1,103 @@
+import pytest
+from pocketbook.store import NoteStore
+from pocketbook.note import Note
+
+
+class TestNoteStoreCreate:
+ def test_create_note(self, notes_dir):
+ store = NoteStore(notes_dir)
+ filename = store.create("My Title", "My Body")
+ assert (notes_dir / filename).exists()
+ content = (notes_dir / filename).read_text()
+ assert content == "My Title\n\nMy Body"
+
+ def test_create_assigns_incrementing_order(self, notes_dir):
+ store = NoteStore(notes_dir)
+ f1 = store.create("First", "")
+ f2 = store.create("Second", "")
+ assert Note.parse_order(f1) == 1
+ assert Note.parse_order(f2) == 2
+
+ def test_create_auto_creates_directory(self, tmp_path):
+ d = tmp_path / "nonexistent" / "pocketbook"
+ store = NoteStore(d)
+ filename = store.create("Test", "body")
+ assert d.exists()
+ assert (d / filename).exists()
+
+
+class TestNoteStoreList:
+ def test_list_empty(self, notes_dir):
+ # Remove the sample file if any fixture created one
+ for f in notes_dir.iterdir():
+ f.unlink()
+ store = NoteStore(notes_dir)
+ assert store.list_notes() == []
+
+ def test_list_returns_sorted_by_order(self, notes_dir):
+ (notes_dir / "0002-20260101-120000-abc12.txt").write_text("B\n\nbody b")
+ (notes_dir / "0001-20260101-120000-def34.txt").write_text("A\n\nbody a")
+ (notes_dir / "0003-20260101-120000-ghi56.txt").write_text("C\n\nbody c")
+ store = NoteStore(notes_dir)
+ notes = store.list_notes()
+ assert len(notes) == 3
+ assert notes[0][1].title == "A"
+ assert notes[1][1].title == "B"
+ assert notes[2][1].title == "C"
+
+ def test_list_skips_non_txt_files(self, notes_dir):
+ (notes_dir / "0001-20260101-120000-abc12.txt").write_text("Note\n\nbody")
+ (notes_dir / "README.md").write_text("not a note")
+ store = NoteStore(notes_dir)
+ assert len(store.list_notes()) == 1
+
+ def test_list_skips_corrupted_filenames(self, notes_dir):
+ (notes_dir / "0001-20260101-120000-abc12.txt").write_text("Good\n\nbody")
+ (notes_dir / "bad-name.txt").write_text("Bad\n\nbody")
+ store = NoteStore(notes_dir)
+ notes = store.list_notes()
+ assert len(notes) == 1
+ assert notes[0][1].title == "Good"
+
+
+class TestNoteStoreUpdate:
+ def test_update_note(self, notes_dir):
+ fname = "0001-20260101-120000-abc12.txt"
+ (notes_dir / fname).write_text("Old Title\n\nOld body")
+ store = NoteStore(notes_dir)
+ store.update(fname, "New Title", "New body")
+ content = (notes_dir / fname).read_text()
+ assert content == "New Title\n\nNew body"
+
+ def test_update_nonexistent_raises(self, notes_dir):
+ store = NoteStore(notes_dir)
+ with pytest.raises(FileNotFoundError):
+ store.update("0099-20260101-120000-nope0.txt", "T", "B")
+
+
+class TestNoteStoreDelete:
+ def test_delete_note(self, notes_dir):
+ fname = "0001-20260101-120000-abc12.txt"
+ (notes_dir / fname).write_text("Delete me\n\nbody")
+ store = NoteStore(notes_dir)
+ store.delete(fname)
+ assert not (notes_dir / fname).exists()
+
+ def test_delete_nonexistent_raises(self, notes_dir):
+ store = NoteStore(notes_dir)
+ with pytest.raises(FileNotFoundError):
+ store.delete("0099-20260101-120000-nope0.txt")
+
+
+class TestNoteStoreReorder:
+ def test_reorder_renumbers_files(self, notes_dir):
+ # Create files with gaps in ordering
+ (notes_dir / "0001-20260101-120000-aaa11.txt").write_text("A\n\na")
+ (notes_dir / "0005-20260101-120000-bbb22.txt").write_text("B\n\nb")
+ (notes_dir / "0010-20260101-120000-ccc33.txt").write_text("C\n\nc")
+ store = NoteStore(notes_dir)
+ store.reorder()
+ notes = store.list_notes()
+ assert Note.parse_order(notes[0][0]) == 1
+ assert Note.parse_order(notes[1][0]) == 2
+ assert Note.parse_order(notes[2][0]) == 3